blob: 0ab5aa1bf6201e32d7fe6d06a5f164b5b81b633a [file] [log] [blame]
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef DFGAbstractInterpreterInlines_h
#define DFGAbstractInterpreterInlines_h
#include <wtf/Platform.h>
#if ENABLE(DFG_JIT)
#include "DFGAbstractInterpreter.h"
#include "GetByIdStatus.h"
#include "Operations.h"
#include "PutByIdStatus.h"
#include "StringObject.h"
namespace JSC { namespace DFG {
template<typename AbstractStateType>
AbstractInterpreter<AbstractStateType>::AbstractInterpreter(Graph& graph, AbstractStateType& state)
: m_codeBlock(graph.m_codeBlock)
, m_graph(graph)
, m_state(state)
{
}
template<typename AbstractStateType>
AbstractInterpreter<AbstractStateType>::~AbstractInterpreter()
{
}
template<typename AbstractStateType>
typename AbstractInterpreter<AbstractStateType>::BooleanResult
AbstractInterpreter<AbstractStateType>::booleanResult(
Node* node, AbstractValue& value)
{
JSValue childConst = value.value();
if (childConst) {
if (childConst.toBoolean(m_codeBlock->globalObjectFor(node->codeOrigin)->globalExec()))
return DefinitelyTrue;
return DefinitelyFalse;
}
// Next check if we can fold because we know that the source is an object or string and does not equal undefined.
if (isCellSpeculation(value.m_type)
&& value.m_currentKnownStructure.hasSingleton()) {
Structure* structure = value.m_currentKnownStructure.singleton();
if (!structure->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->codeOrigin))
&& structure->typeInfo().type() != StringType)
return DefinitelyTrue;
}
return UnknownBooleanResult;
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::startExecuting(Node* node)
{
ASSERT(m_state.block());
ASSERT(m_state.isValid());
m_state.setDidClobber(false);
node->setCanExit(false);
return node->shouldGenerate();
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::startExecuting(unsigned indexInBlock)
{
return startExecuting(m_state.block()->at(indexInBlock));
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::executeEdges(Node* node)
{
DFG_NODE_DO_TO_CHILDREN(m_graph, node, filterEdgeByUse);
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::executeEdges(unsigned indexInBlock)
{
executeEdges(m_state.block()->at(indexInBlock));
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::verifyEdge(Node*, Edge edge)
{
RELEASE_ASSERT(!(forNode(edge).m_type & ~typeFilterFor(edge.useKind())));
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::verifyEdges(Node* node)
{
DFG_NODE_DO_TO_CHILDREN(m_graph, node, verifyEdge);
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimit, Node* node)
{
if (!ASSERT_DISABLED)
verifyEdges(node);
m_state.createValueForNode(node);
switch (node->op()) {
case JSConstant:
case WeakJSConstant:
case PhantomArguments: {
forNode(node).set(m_graph, m_graph.valueOfJSConstant(node));
break;
}
case Identity: {
forNode(node) = forNode(node->child1());
break;
}
case GetArgument: {
ASSERT(m_graph.m_form == SSA);
VariableAccessData* variable = node->variableAccessData();
AbstractValue& value = m_state.variables().operand(variable->local().offset());
ASSERT(value.isHeapTop());
FiltrationResult result =
value.filter(typeFilterFor(useKindFor(variable->flushFormat())));
ASSERT_UNUSED(result, result == FiltrationOK);
forNode(node) = value;
break;
}
case ExtractOSREntryLocal: {
if (!(node->unlinkedLocal().isArgument())
&& m_graph.m_lazyVars.get(node->unlinkedLocal().toLocal())) {
// This is kind of pessimistic - we could know in some cases that the
// DFG code at the point of the OSR had already initialized the lazy
// variable. But maybe this is fine, since we're inserting OSR
// entrypoints very early in the pipeline - so any lazy initializations
// ought to be hoisted out anyway.
forNode(node).makeBytecodeTop();
} else
forNode(node).makeHeapTop();
break;
}
case GetLocal: {
VariableAccessData* variableAccessData = node->variableAccessData();
if (variableAccessData->prediction() == SpecNone) {
m_state.setIsValid(false);
break;
}
AbstractValue value = m_state.variables().operand(variableAccessData->local().offset());
if (!variableAccessData->isCaptured()) {
if (value.isClear())
node->setCanExit(true);
}
if (value.value())
m_state.setFoundConstants(true);
forNode(node) = value;
break;
}
case GetLocalUnlinked: {
AbstractValue value = m_state.variables().operand(node->unlinkedLocal().offset());
if (value.value())
m_state.setFoundConstants(true);
forNode(node) = value;
break;
}
case SetLocal: {
m_state.variables().operand(node->local().offset()) = forNode(node->child1());
break;
}
case MovHint:
case MovHintAndCheck: {
// Don't need to do anything. A MovHint is effectively a promise that the SetLocal
// was dead.
break;
}
case ZombieHint: {
RELEASE_ASSERT_NOT_REACHED();
break;
}
case SetArgument:
// Assert that the state of arguments has been set.
ASSERT(!m_state.block()->valuesAtHead.operand(node->local()).isClear());
break;
case BitAnd:
case BitOr:
case BitXor:
case BitRShift:
case BitLShift:
case BitURShift: {
JSValue left = forNode(node->child1()).value();
JSValue right = forNode(node->child2()).value();
if (left && right && left.isInt32() && right.isInt32()) {
int32_t a = left.asInt32();
int32_t b = right.asInt32();
switch (node->op()) {
case BitAnd:
setConstant(node, JSValue(a & b));
break;
case BitOr:
setConstant(node, JSValue(a | b));
break;
case BitXor:
setConstant(node, JSValue(a ^ b));
break;
case BitRShift:
setConstant(node, JSValue(a >> static_cast<uint32_t>(b)));
break;
case BitLShift:
setConstant(node, JSValue(a << static_cast<uint32_t>(b)));
break;
case BitURShift:
setConstant(node, JSValue(static_cast<uint32_t>(a) >> static_cast<uint32_t>(b)));
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
forNode(node).setType(SpecInt32);
break;
}
case UInt32ToNumber: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
ASSERT(child.isInt32());
uint32_t value = child.asInt32();
setConstant(node, jsNumber(value));
break;
}
if (!node->canSpeculateInt32())
forNode(node).setType(SpecDouble);
else {
forNode(node).setType(SpecInt32);
node->setCanExit(true);
}
break;
}
case DoubleAsInt32: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
double asDouble = child.asNumber();
int32_t asInt = JSC::toInt32(asDouble);
if (bitwise_cast<int64_t>(static_cast<double>(asInt)) == bitwise_cast<int64_t>(asDouble)) {
setConstant(node, JSValue(asInt));
break;
}
}
node->setCanExit(true);
forNode(node).setType(SpecInt32);
break;
}
case ValueToInt32: {
JSValue child = forNode(node->child1()).value();
if (child) {
if (child.isNumber()) {
if (child.isInt32())
setConstant(node, child);
else
setConstant(node, JSValue(JSC::toInt32(child.asDouble())));
break;
}
if (child.isBoolean()) {
setConstant(node, JSValue(child.asBoolean()));
break;
}
}
forNode(node).setType(SpecInt32);
break;
}
case Int32ToDouble: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, JSValue(JSValue::EncodeAsDouble, child.asNumber()));
break;
}
if (isInt32Speculation(forNode(node->child1()).m_type))
forNode(node).setType(SpecDoubleReal);
else
forNode(node).setType(SpecDouble);
break;
}
case Int52ToDouble: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, child);
break;
}
forNode(node).setType(SpecDouble);
break;
}
case Int52ToValue: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, child);
break;
}
SpeculatedType type = forNode(node->child1()).m_type;
if (type & SpecInt52)
type = (type | SpecInt32 | SpecInt52AsDouble) & ~SpecInt52;
forNode(node).setType(type);
break;
}
case ValueAdd:
case ArithAdd: {
JSValue left = forNode(node->child1()).value();
JSValue right = forNode(node->child2()).value();
if (left && right && left.isNumber() && right.isNumber()) {
setConstant(node, JSValue(left.asNumber() + right.asNumber()));
break;
}
switch (node->binaryUseKind()) {
case Int32Use:
forNode(node).setType(SpecInt32);
if (!bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setCanExit(true);
break;
case MachineIntUse:
forNode(node).setType(SpecInt52);
if (!forNode(node->child1()).isType(SpecInt32)
|| !forNode(node->child2()).isType(SpecInt32))
node->setCanExit(true);
break;
case NumberUse:
if (isFullRealNumberSpeculation(forNode(node->child1()).m_type)
&& isFullRealNumberSpeculation(forNode(node->child2()).m_type))
forNode(node).setType(SpecDoubleReal);
else
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT(node->op() == ValueAdd);
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).setType(SpecString | SpecBytecodeNumber);
break;
}
break;
}
case MakeRope: {
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
}
case ArithSub: {
JSValue left = forNode(node->child1()).value();
JSValue right = forNode(node->child2()).value();
if (left && right && left.isNumber() && right.isNumber()) {
setConstant(node, JSValue(left.asNumber() - right.asNumber()));
break;
}
switch (node->binaryUseKind()) {
case Int32Use:
forNode(node).setType(SpecInt32);
if (!bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setCanExit(true);
break;
case MachineIntUse:
forNode(node).setType(SpecInt52);
if (!forNode(node->child1()).isType(SpecInt32)
|| !forNode(node->child2()).isType(SpecInt32))
node->setCanExit(true);
break;
case NumberUse:
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case ArithNegate: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, JSValue(-child.asNumber()));
break;
}
switch (node->child1().useKind()) {
case Int32Use:
forNode(node).setType(SpecInt32);
if (!bytecodeCanTruncateInteger(node->arithNodeFlags()))
node->setCanExit(true);
break;
case MachineIntUse:
forNode(node).setType(SpecInt52);
if (m_state.forNode(node->child1()).couldBeType(SpecInt52))
node->setCanExit(true);
if (!bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setCanExit(true);
break;
case NumberUse:
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case ArithMul: {
JSValue left = forNode(node->child1()).value();
JSValue right = forNode(node->child2()).value();
if (left && right && left.isNumber() && right.isNumber()) {
setConstant(node, JSValue(left.asNumber() * right.asNumber()));
break;
}
switch (node->binaryUseKind()) {
case Int32Use:
forNode(node).setType(SpecInt32);
if (!bytecodeCanTruncateInteger(node->arithNodeFlags())
|| !bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
node->setCanExit(true);
break;
case MachineIntUse:
forNode(node).setType(SpecInt52);
node->setCanExit(true);
break;
case NumberUse:
if (isFullRealNumberSpeculation(forNode(node->child1()).m_type)
|| isFullRealNumberSpeculation(forNode(node->child2()).m_type))
forNode(node).setType(SpecDoubleReal);
else
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case ArithIMul: {
forNode(node).setType(SpecInt32);
break;
}
case ArithDiv:
case ArithMin:
case ArithMax:
case ArithMod: {
JSValue left = forNode(node->child1()).value();
JSValue right = forNode(node->child2()).value();
if (node->op() == ArithMod && right && right.isNumber() && right.asNumber() == 1) {
setConstant(node, JSValue(0));
break;
}
if (left && right && left.isNumber() && right.isNumber()) {
double a = left.asNumber();
double b = right.asNumber();
switch (node->op()) {
case ArithDiv:
setConstant(node, JSValue(a / b));
break;
case ArithMin:
setConstant(node, JSValue(a < b ? a : (b <= a ? b : a + b)));
break;
case ArithMax:
setConstant(node, JSValue(a > b ? a : (b >= a ? b : a + b)));
break;
case ArithMod:
setConstant(node, JSValue(fmod(a, b)));
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
switch (node->binaryUseKind()) {
case Int32Use:
forNode(node).setType(SpecInt32);
node->setCanExit(true);
break;
case NumberUse:
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case ArithAbs: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, JSValue(fabs(child.asNumber())));
break;
}
switch (node->child1().useKind()) {
case Int32Use:
forNode(node).setType(SpecInt32);
node->setCanExit(true);
break;
case NumberUse:
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case ArithSqrt: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, jsNumber(sqrt(child.asNumber())));
break;
}
forNode(node).setType(SpecDouble);
break;
}
case ArithSin: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, jsNumber(sin(child.asNumber())));
break;
}
forNode(node).setType(SpecDouble);
break;
}
case ArithCos: {
JSValue child = forNode(node->child1()).value();
if (child && child.isNumber()) {
setConstant(node, jsNumber(cos(child.asNumber())));
break;
}
forNode(node).setType(SpecDouble);
break;
}
case LogicalNot: {
switch (booleanResult(node, forNode(node->child1()))) {
case DefinitelyTrue:
setConstant(node, jsBoolean(false));
break;
case DefinitelyFalse:
setConstant(node, jsBoolean(true));
break;
default:
switch (node->child1().useKind()) {
case BooleanUse:
case Int32Use:
case NumberUse:
case UntypedUse:
case StringUse:
break;
case ObjectOrOtherUse:
node->setCanExit(true);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
forNode(node).setType(SpecBoolean);
break;
}
break;
}
case IsUndefined:
case IsBoolean:
case IsNumber:
case IsString:
case IsObject:
case IsFunction: {
node->setCanExit(
node->op() == IsUndefined
&& m_graph.masqueradesAsUndefinedWatchpointIsStillValid(node->codeOrigin));
JSValue child = forNode(node->child1()).value();
if (child) {
bool constantWasSet = true;
switch (node->op()) {
case IsUndefined:
setConstant(node, jsBoolean(
child.isCell()
? child.asCell()->structure()->masqueradesAsUndefined(m_codeBlock->globalObjectFor(node->codeOrigin))
: child.isUndefined()));
break;
case IsBoolean:
setConstant(node, jsBoolean(child.isBoolean()));
break;
case IsNumber:
setConstant(node, jsBoolean(child.isNumber()));
break;
case IsString:
setConstant(node, jsBoolean(isJSString(child)));
break;
case IsObject:
if (child.isNull() || !child.isObject()) {
setConstant(node, jsBoolean(child.isNull()));
break;
}
constantWasSet = false;
break;
default:
constantWasSet = false;
break;
}
if (constantWasSet)
break;
}
forNode(node).setType(SpecBoolean);
break;
}
case TypeOf: {
VM* vm = m_codeBlock->vm();
JSValue child = forNode(node->child1()).value();
AbstractValue& abstractChild = forNode(node->child1());
if (child) {
JSValue typeString = jsTypeStringForValue(*vm, m_codeBlock->globalObjectFor(node->codeOrigin), child);
setConstant(node, typeString);
break;
}
if (isFullNumberSpeculation(abstractChild.m_type)) {
setConstant(node, vm->smallStrings.numberString());
break;
}
if (isStringSpeculation(abstractChild.m_type)) {
setConstant(node, vm->smallStrings.stringString());
break;
}
if (isFinalObjectSpeculation(abstractChild.m_type) || isArraySpeculation(abstractChild.m_type) || isArgumentsSpeculation(abstractChild.m_type)) {
setConstant(node, vm->smallStrings.objectString());
break;
}
if (isFunctionSpeculation(abstractChild.m_type)) {
setConstant(node, vm->smallStrings.functionString());
break;
}
if (isBooleanSpeculation(abstractChild.m_type)) {
setConstant(node, vm->smallStrings.booleanString());
break;
}
switch (node->child1().useKind()) {
case StringUse:
case CellUse:
node->setCanExit(true);
break;
case UntypedUse:
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
}
case CompareLess:
case CompareLessEq:
case CompareGreater:
case CompareGreaterEq:
case CompareEq:
case CompareEqConstant: {
JSValue leftConst = forNode(node->child1()).value();
JSValue rightConst = forNode(node->child2()).value();
if (leftConst && rightConst) {
if (leftConst.isNumber() && rightConst.isNumber()) {
double a = leftConst.asNumber();
double b = rightConst.asNumber();
switch (node->op()) {
case CompareLess:
setConstant(node, jsBoolean(a < b));
break;
case CompareLessEq:
setConstant(node, jsBoolean(a <= b));
break;
case CompareGreater:
setConstant(node, jsBoolean(a > b));
break;
case CompareGreaterEq:
setConstant(node, jsBoolean(a >= b));
break;
case CompareEq:
setConstant(node, jsBoolean(a == b));
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
if (node->op() == CompareEq && leftConst.isString() && rightConst.isString()) {
const StringImpl* a = asString(leftConst)->tryGetValueImpl();
const StringImpl* b = asString(rightConst)->tryGetValueImpl();
if (a && b) {
setConstant(node, jsBoolean(WTF::equal(a, b)));
break;
}
}
}
if (node->op() == CompareEqConstant || node->op() == CompareEq) {
SpeculatedType leftType = forNode(node->child1()).m_type;
SpeculatedType rightType = forNode(node->child2()).m_type;
if ((isInt32Speculation(leftType) && isOtherSpeculation(rightType))
|| (isOtherSpeculation(leftType) && isInt32Speculation(rightType))) {
setConstant(node, jsBoolean(false));
break;
}
}
forNode(node).setType(SpecBoolean);
// This is overly conservative. But the only thing this prevents is store elimination,
// and how likely is it, really, that you'll have redundant stores across a comparison
// operation? Comparison operations are typically at the end of basic blocks, so
// unless we have global store elimination (super unlikely given how unprofitable that
// optimization is to begin with), you aren't going to be wanting to store eliminate
// across an equality op.
node->setCanExit(true);
break;
}
case CompareStrictEq:
case CompareStrictEqConstant: {
Node* leftNode = node->child1().node();
Node* rightNode = node->child2().node();
JSValue left = forNode(leftNode).value();
JSValue right = forNode(rightNode).value();
if (left && right) {
if (left.isNumber() && right.isNumber()) {
setConstant(node, jsBoolean(left.asNumber() == right.asNumber()));
break;
}
if (left.isString() && right.isString()) {
const StringImpl* a = asString(left)->tryGetValueImpl();
const StringImpl* b = asString(right)->tryGetValueImpl();
if (a && b) {
setConstant(node, jsBoolean(WTF::equal(a, b)));
break;
}
}
}
forNode(node).setType(SpecBoolean);
node->setCanExit(true); // This is overly conservative.
break;
}
case StringCharCodeAt:
node->setCanExit(true);
forNode(node).setType(SpecInt32);
break;
case StringFromCharCode:
forNode(node).setType(SpecString);
break;
case StringCharAt:
node->setCanExit(true);
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
case GetByVal: {
node->setCanExit(true);
switch (node->arrayMode().type()) {
case Array::SelectUsingPredictions:
case Array::Unprofiled:
case Array::Undecided:
RELEASE_ASSERT_NOT_REACHED();
break;
case Array::ForceExit:
m_state.setIsValid(false);
break;
case Array::Generic:
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
break;
case Array::String:
if (node->arrayMode().isOutOfBounds()) {
// If the watchpoint was still valid we could totally set this to be
// SpecString | SpecOther. Except that we'd have to be careful. If we
// tested the watchpoint state here then it could change by the time
// we got to the backend. So to do this right, we'd have to get the
// fixup phase to check the watchpoint state and then bake into the
// GetByVal operation the fact that we're using a watchpoint, using
// something like Array::SaneChain (except not quite, because that
// implies an in-bounds access). None of this feels like it's worth it,
// so we're going with TOP for now. The same thing applies to
// clobbering the world.
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
} else
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
case Array::Arguments:
forNode(node).makeHeapTop();
break;
case Array::Int32:
if (node->arrayMode().isOutOfBounds()) {
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
} else
forNode(node).setType(SpecInt32);
break;
case Array::Double:
if (node->arrayMode().isOutOfBounds()) {
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
} else if (node->arrayMode().isSaneChain())
forNode(node).setType(SpecDouble);
else
forNode(node).setType(SpecDoubleReal);
break;
case Array::Contiguous:
case Array::ArrayStorage:
case Array::SlowPutArrayStorage:
if (node->arrayMode().isOutOfBounds())
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
break;
case Array::Int8Array:
forNode(node).setType(SpecInt32);
break;
case Array::Int16Array:
forNode(node).setType(SpecInt32);
break;
case Array::Int32Array:
forNode(node).setType(SpecInt32);
break;
case Array::Uint8Array:
forNode(node).setType(SpecInt32);
break;
case Array::Uint8ClampedArray:
forNode(node).setType(SpecInt32);
break;
case Array::Uint16Array:
forNode(node).setType(SpecInt32);
break;
case Array::Uint32Array:
if (node->shouldSpeculateInt32())
forNode(node).setType(SpecInt32);
else if (enableInt52() && node->shouldSpeculateMachineInt())
forNode(node).setType(SpecInt52);
else
forNode(node).setType(SpecDouble);
break;
case Array::Float32Array:
forNode(node).setType(SpecDouble);
break;
case Array::Float64Array:
forNode(node).setType(SpecDouble);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case PutByValDirect:
case PutByVal:
case PutByValAlias: {
node->setCanExit(true);
switch (node->arrayMode().modeForPut().type()) {
case Array::ForceExit:
m_state.setIsValid(false);
break;
case Array::Generic:
clobberWorld(node->codeOrigin, clobberLimit);
break;
case Array::Int32:
if (node->arrayMode().isOutOfBounds())
clobberWorld(node->codeOrigin, clobberLimit);
break;
case Array::Double:
if (node->arrayMode().isOutOfBounds())
clobberWorld(node->codeOrigin, clobberLimit);
break;
case Array::Contiguous:
case Array::ArrayStorage:
if (node->arrayMode().isOutOfBounds())
clobberWorld(node->codeOrigin, clobberLimit);
break;
case Array::SlowPutArrayStorage:
if (node->arrayMode().mayStoreToHole())
clobberWorld(node->codeOrigin, clobberLimit);
break;
default:
break;
}
break;
}
case ArrayPush:
node->setCanExit(true);
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).setType(SpecBytecodeNumber);
break;
case ArrayPop:
node->setCanExit(true);
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
break;
case RegExpExec:
forNode(node).makeHeapTop();
break;
case RegExpTest:
forNode(node).setType(SpecBoolean);
break;
case Jump:
break;
case Branch: {
Node* child = node->child1().node();
BooleanResult result = booleanResult(node, forNode(child));
if (result == DefinitelyTrue) {
m_state.setBranchDirection(TakeTrue);
break;
}
if (result == DefinitelyFalse) {
m_state.setBranchDirection(TakeFalse);
break;
}
// FIXME: The above handles the trivial cases of sparse conditional
// constant propagation, but we can do better:
// We can specialize the source variable's value on each direction of
// the branch.
node->setCanExit(true); // This is overly conservative.
m_state.setBranchDirection(TakeBoth);
break;
}
case Switch: {
// Nothing to do for now.
// FIXME: Do sparse conditional things.
break;
}
case Return:
m_state.setIsValid(false);
break;
case Throw:
case ThrowReferenceError:
m_state.setIsValid(false);
node->setCanExit(true);
break;
case ToPrimitive: {
JSValue childConst = forNode(node->child1()).value();
if (childConst && childConst.isNumber()) {
setConstant(node, childConst);
break;
}
ASSERT(node->child1().useKind() == UntypedUse);
AbstractValue& source = forNode(node->child1());
AbstractValue& destination = forNode(node);
// NB. The more canonical way of writing this would have been:
//
// destination = source;
// if (destination.m_type & !(SpecFullNumber | SpecString | SpecBoolean)) {
// destination.filter(SpecFullNumber | SpecString | SpecBoolean);
// AbstractValue string;
// string.set(vm->stringStructure);
// destination.merge(string);
// }
//
// The reason why this would, in most other cases, have been better is that
// then destination would preserve any non-SpeculatedType knowledge of source.
// As it stands, the code below forgets any non-SpeculatedType knowledge that
// source would have had. Fortunately, though, for things like strings and
// numbers and booleans, we don't care about the non-SpeculatedType knowedge:
// the structure won't tell us anything we don't already know, and neither
// will ArrayModes. And if the source was a meaningful constant then we
// would have handled that above. Unfortunately, this does mean that
// ToPrimitive will currently forget string constants. But that's not a big
// deal since we don't do any optimization on those currently.
clobberWorld(node->codeOrigin, clobberLimit);
SpeculatedType type = source.m_type;
if (type & ~(SpecFullNumber | SpecString | SpecBoolean))
type = (SpecHeapTop & ~SpecCell) | SpecString;
destination.setType(type);
if (destination.isClear())
m_state.setIsValid(false);
break;
}
case ToString: {
switch (node->child1().useKind()) {
case StringObjectUse:
// This also filters that the StringObject has the primordial StringObject
// structure.
filter(
node->child1(),
m_graph.globalObjectFor(node->codeOrigin)->stringObjectStructure());
node->setCanExit(true); // We could be more precise but it's likely not worth it.
break;
case StringOrStringObjectUse:
node->setCanExit(true); // We could be more precise but it's likely not worth it.
break;
case CellUse:
case UntypedUse:
clobberWorld(node->codeOrigin, clobberLimit);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
}
case NewStringObject: {
ASSERT(node->structure()->classInfo() == StringObject::info());
forNode(node).set(m_graph, node->structure());
break;
}
case NewArray:
node->setCanExit(true);
forNode(node).set(
m_graph,
m_graph.globalObjectFor(node->codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()));
m_state.setHaveStructures(true);
break;
case NewArrayBuffer:
node->setCanExit(true);
forNode(node).set(
m_graph,
m_graph.globalObjectFor(node->codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()));
m_state.setHaveStructures(true);
break;
case NewArrayWithSize:
node->setCanExit(true);
forNode(node).setType(SpecArray);
m_state.setHaveStructures(true);
break;
case NewTypedArray:
switch (node->child1().useKind()) {
case Int32Use:
break;
case UntypedUse:
clobberWorld(node->codeOrigin, clobberLimit);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
forNode(node).set(
m_graph,
m_graph.globalObjectFor(node->codeOrigin)->typedArrayStructure(
node->typedArrayType()));
m_state.setHaveStructures(true);
break;
case NewRegexp:
forNode(node).set(m_graph, m_graph.globalObjectFor(node->codeOrigin)->regExpStructure());
m_state.setHaveStructures(true);
break;
case ToThis: {
AbstractValue& source = forNode(node->child1());
AbstractValue& destination = forNode(node);
if (m_graph.executableFor(node->codeOrigin)->isStrictMode())
destination.makeHeapTop();
else {
destination = source;
destination.merge(SpecObject);
}
break;
}
case CreateThis: {
forNode(node).setType(SpecFinalObject);
break;
}
case AllocationProfileWatchpoint:
node->setCanExit(true);
break;
case NewObject:
forNode(node).set(m_graph, node->structure());
m_state.setHaveStructures(true);
break;
case CreateActivation:
forNode(node).set(
m_graph, m_codeBlock->globalObjectFor(node->codeOrigin)->activationStructure());
m_state.setHaveStructures(true);
break;
case CreateArguments:
forNode(node).setType(SpecArguments);
break;
case TearOffActivation:
case TearOffArguments:
// Does nothing that is user-visible.
break;
case CheckArgumentsNotCreated:
if (isEmptySpeculation(
m_state.variables().operand(
m_graph.argumentsRegisterFor(node->codeOrigin).offset()).m_type))
m_state.setFoundConstants(true);
else
node->setCanExit(true);
break;
case GetMyArgumentsLength:
// We know that this executable does not escape its arguments, so we can optimize
// the arguments a bit. Note that this is not sufficient to force constant folding
// of GetMyArgumentsLength, because GetMyArgumentsLength is a clobbering operation.
// We perform further optimizations on this later on.
if (node->codeOrigin.inlineCallFrame) {
forNode(node).set(
m_graph, jsNumber(node->codeOrigin.inlineCallFrame->arguments.size() - 1));
} else
forNode(node).setType(SpecInt32);
node->setCanExit(
!isEmptySpeculation(
m_state.variables().operand(
m_graph.argumentsRegisterFor(node->codeOrigin)).m_type));
break;
case GetMyArgumentsLengthSafe:
// This potentially clobbers all structures if the arguments object had a getter
// installed on the length property.
clobberWorld(node->codeOrigin, clobberLimit);
// We currently make no guarantee about what this returns because it does not
// speculate that the length property is actually a length.
forNode(node).makeHeapTop();
break;
case GetMyArgumentByVal:
node->setCanExit(true);
// We know that this executable does not escape its arguments, so we can optimize
// the arguments a bit. Note that this ends up being further optimized by the
// ArgumentsSimplificationPhase.
forNode(node).makeHeapTop();
break;
case GetMyArgumentByValSafe:
node->setCanExit(true);
// This potentially clobbers all structures if the property we're accessing has
// a getter. We don't speculate against this.
clobberWorld(node->codeOrigin, clobberLimit);
// And the result is unknown.
forNode(node).makeHeapTop();
break;
case NewFunction: {
AbstractValue& value = forNode(node);
value = forNode(node->child1());
if (!(value.m_type & SpecEmpty)) {
m_state.setFoundConstants(true);
break;
}
value.setType((value.m_type & ~SpecEmpty) | SpecFunction);
break;
}
case NewFunctionExpression:
case NewFunctionNoCheck:
forNode(node).set(
m_graph, m_codeBlock->globalObjectFor(node->codeOrigin)->functionStructure());
break;
case GetCallee:
forNode(node).setType(SpecFunction);
break;
case GetScope: // FIXME: We could get rid of these if we know that the JSFunction is a constant. https://bugs.webkit.org/show_bug.cgi?id=106202
case GetMyScope:
case SkipTopScope:
forNode(node).setType(SpecObjectOther);
break;
case SkipScope: {
JSValue child = forNode(node->child1()).value();
if (child) {
setConstant(node, JSValue(jsCast<JSScope*>(child.asCell())->next()));
break;
}
forNode(node).setType(SpecObjectOther);
break;
}
case GetClosureRegisters:
forNode(node).clear(); // The result is not a JS value.
break;
case GetClosureVar:
forNode(node).makeHeapTop();
break;
case PutClosureVar:
clobberCapturedVars(node->codeOrigin);
break;
case GetById:
case GetByIdFlush:
node->setCanExit(true);
if (!node->prediction()) {
m_state.setIsValid(false);
break;
}
if (isCellSpeculation(node->child1()->prediction())) {
if (Structure* structure = forNode(node->child1()).bestProvenStructure()) {
GetByIdStatus status = GetByIdStatus::computeFor(
m_graph.m_vm, structure,
m_graph.identifiers()[node->identifierNumber()]);
if (status.isSimple()) {
// Assert things that we can't handle and that the computeFor() method
// above won't be able to return.
ASSERT(status.structureSet().size() == 1);
ASSERT(!status.chain());
if (status.specificValue())
setConstant(node, status.specificValue());
else
forNode(node).makeHeapTop();
filter(node->child1(), status.structureSet());
m_state.setFoundConstants(true);
break;
}
}
}
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
break;
case GetArrayLength:
node->setCanExit(true); // Lies, but it's true for the common case of JSArray, so it's good enough.
forNode(node).setType(SpecInt32);
break;
case CheckExecutable: {
// FIXME: We could track executables in AbstractValue, which would allow us to get rid of these checks
// more thoroughly. https://bugs.webkit.org/show_bug.cgi?id=106200
// FIXME: We could eliminate these entirely if we know the exact value that flows into this.
// https://bugs.webkit.org/show_bug.cgi?id=106201
node->setCanExit(true);
break;
}
case CheckStructure: {
// FIXME: We should be able to propagate the structure sets of constants (i.e. prototypes).
AbstractValue& value = forNode(node->child1());
ASSERT(!(value.m_type & ~SpecCell)); // Edge filtering should have already ensured this.
StructureSet& set = node->structureSet();
if (value.m_currentKnownStructure.isSubsetOf(set)) {
m_state.setFoundConstants(true);
break;
}
node->setCanExit(true);
m_state.setHaveStructures(true);
// If this structure check is attempting to prove knowledge already held in
// the futurePossibleStructure set then the constant folding phase should
// turn this into a watchpoint instead.
if (value.m_futurePossibleStructure.isSubsetOf(set)
&& value.m_futurePossibleStructure.hasSingleton()) {
m_state.setFoundConstants(true);
filter(value, value.m_futurePossibleStructure.singleton());
break;
}
filter(value, set);
break;
}
case StructureTransitionWatchpoint: {
AbstractValue& value = forNode(node->child1());
filter(value, node->structure());
m_state.setHaveStructures(true);
node->setCanExit(true);
break;
}
case PutStructure:
case PhantomPutStructure:
if (!forNode(node->child1()).m_currentKnownStructure.isClear()) {
clobberStructures(clobberLimit);
forNode(node->child1()).set(m_graph, node->structureTransitionData().newStructure);
m_state.setHaveStructures(true);
}
break;
case GetButterfly:
case AllocatePropertyStorage:
case ReallocatePropertyStorage:
forNode(node).clear(); // The result is not a JS value.
break;
case CheckArray: {
if (node->arrayMode().alreadyChecked(m_graph, node, forNode(node->child1()))) {
m_state.setFoundConstants(true);
break;
}
node->setCanExit(true); // Lies, but this is followed by operations (like GetByVal) that always exit, so there is no point in us trying to be clever here.
switch (node->arrayMode().type()) {
case Array::String:
filter(node->child1(), SpecString);
break;
case Array::Int32:
case Array::Double:
case Array::Contiguous:
case Array::ArrayStorage:
case Array::SlowPutArrayStorage:
break;
case Array::Arguments:
filter(node->child1(), SpecArguments);
break;
case Array::Int8Array:
filter(node->child1(), SpecInt8Array);
break;
case Array::Int16Array:
filter(node->child1(), SpecInt16Array);
break;
case Array::Int32Array:
filter(node->child1(), SpecInt32Array);
break;
case Array::Uint8Array:
filter(node->child1(), SpecUint8Array);
break;
case Array::Uint8ClampedArray:
filter(node->child1(), SpecUint8ClampedArray);
break;
case Array::Uint16Array:
filter(node->child1(), SpecUint16Array);
break;
case Array::Uint32Array:
filter(node->child1(), SpecUint32Array);
break;
case Array::Float32Array:
filter(node->child1(), SpecFloat32Array);
break;
case Array::Float64Array:
filter(node->child1(), SpecFloat64Array);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering());
m_state.setHaveStructures(true);
break;
}
case Arrayify: {
if (node->arrayMode().alreadyChecked(m_graph, node, forNode(node->child1()))) {
m_state.setFoundConstants(true);
break;
}
ASSERT(node->arrayMode().conversion() == Array::Convert
|| node->arrayMode().conversion() == Array::RageConvert);
node->setCanExit(true);
clobberStructures(clobberLimit);
filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering());
m_state.setHaveStructures(true);
break;
}
case ArrayifyToStructure: {
AbstractValue& value = forNode(node->child1());
StructureSet set = node->structure();
if (value.m_futurePossibleStructure.isSubsetOf(set)
|| value.m_currentKnownStructure.isSubsetOf(set))
m_state.setFoundConstants(true);
node->setCanExit(true);
clobberStructures(clobberLimit);
filter(value, set);
m_state.setHaveStructures(true);
break;
}
case GetIndexedPropertyStorage: {
forNode(node).clear();
break;
}
case GetTypedArrayByteOffset: {
forNode(node).setType(SpecInt32);
break;
}
case GetByOffset: {
forNode(node).makeHeapTop();
break;
}
case PutByOffset: {
break;
}
case CheckFunction: {
JSValue value = forNode(node->child1()).value();
if (value == node->function()) {
m_state.setFoundConstants(true);
ASSERT(value);
break;
}
node->setCanExit(true); // Lies! We can do better.
filterByValue(node->child1(), node->function());
break;
}
case PutById:
case PutByIdDirect:
node->setCanExit(true);
if (Structure* structure = forNode(node->child1()).bestProvenStructure()) {
PutByIdStatus status = PutByIdStatus::computeFor(
m_graph.m_vm,
m_graph.globalObjectFor(node->codeOrigin),
structure,
m_graph.identifiers()[node->identifierNumber()],
node->op() == PutByIdDirect);
if (status.isSimpleReplace()) {
filter(node->child1(), structure);
m_state.setFoundConstants(true);
break;
}
if (status.isSimpleTransition()) {
clobberStructures(clobberLimit);
forNode(node->child1()).set(m_graph, status.newStructure());
m_state.setHaveStructures(true);
m_state.setFoundConstants(true);
break;
}
}
clobberWorld(node->codeOrigin, clobberLimit);
break;
case In:
// FIXME: We can determine when the property definitely exists based on abstract
// value information.
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).setType(SpecBoolean);
break;
case GetGlobalVar:
forNode(node).makeHeapTop();
break;
case GlobalVarWatchpoint:
case VarInjectionWatchpoint:
node->setCanExit(true);
break;
case PutGlobalVar:
break;
case CheckHasInstance:
node->setCanExit(true);
// Sadly, we don't propagate the fact that we've done CheckHasInstance
break;
case InstanceOf:
node->setCanExit(true);
// Again, sadly, we don't propagate the fact that we've done InstanceOf
forNode(node).setType(SpecBoolean);
break;
case Phi:
RELEASE_ASSERT(m_graph.m_form == SSA);
// The state of this node would have already been decided.
break;
case Upsilon: {
m_state.createValueForNode(node->phi());
AbstractValue& value = forNode(node->child1());
forNode(node) = value;
forNode(node->phi()) = value;
break;
}
case Flush:
case PhantomLocal:
case Breakpoint:
break;
case Call:
case Construct:
node->setCanExit(true);
clobberWorld(node->codeOrigin, clobberLimit);
forNode(node).makeHeapTop();
break;
case ForceOSRExit:
node->setCanExit(true);
m_state.setIsValid(false);
break;
case InvalidationPoint:
node->setCanExit(true);
break;
case CheckWatchdogTimer:
node->setCanExit(true);
break;
case Phantom:
case CountExecution:
case CheckTierUpInLoop:
case CheckTierUpAtReturn:
break;
case CheckTierUpAndOSREnter:
case LoopHint:
// We pretend that it can exit because it may want to get all state.
node->setCanExit(true);
break;
case Unreachable:
RELEASE_ASSERT_NOT_REACHED();
break;
case LastNodeType:
RELEASE_ASSERT_NOT_REACHED();
break;
}
return m_state.isValid();
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned indexInBlock)
{
return executeEffects(indexInBlock, m_state.block()->at(indexInBlock));
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::execute(unsigned indexInBlock)
{
Node* node = m_state.block()->at(indexInBlock);
if (!startExecuting(node))
return true;
executeEdges(node);
return executeEffects(indexInBlock, node);
}
template<typename AbstractStateType>
bool AbstractInterpreter<AbstractStateType>::execute(Node* node)
{
if (!startExecuting(node))
return true;
executeEdges(node);
return executeEffects(UINT_MAX, node);
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::clobberWorld(
const CodeOrigin& codeOrigin, unsigned clobberLimit)
{
clobberCapturedVars(codeOrigin);
clobberStructures(clobberLimit);
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::clobberCapturedVars(const CodeOrigin& codeOrigin)
{
if (codeOrigin.inlineCallFrame) {
const BitVector& capturedVars = codeOrigin.inlineCallFrame->capturedVars;
for (size_t i = capturedVars.size(); i--;) {
if (!capturedVars.quickGet(i))
continue;
m_state.variables().local(i).makeHeapTop();
}
} else {
for (size_t i = m_codeBlock->m_numVars; i--;) {
if (m_codeBlock->isCaptured(virtualRegisterForLocal(i)))
m_state.variables().local(i).makeHeapTop();
}
}
for (size_t i = m_state.variables().numberOfArguments(); i--;) {
if (m_codeBlock->isCaptured(virtualRegisterForArgument(i)))
m_state.variables().argument(i).makeHeapTop();
}
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::clobberStructures(unsigned clobberLimit)
{
if (!m_state.haveStructures())
return;
if (clobberLimit >= m_state.block()->size())
clobberLimit = m_state.block()->size();
else
clobberLimit++;
ASSERT(clobberLimit <= m_state.block()->size());
for (size_t i = clobberLimit; i--;)
forNode(m_state.block()->at(i)).clobberStructures();
if (m_graph.m_form == SSA) {
HashSet<Node*>::iterator iter = m_state.block()->ssa->liveAtHead.begin();
HashSet<Node*>::iterator end = m_state.block()->ssa->liveAtHead.end();
for (; iter != end; ++iter)
forNode(*iter).clobberStructures();
}
for (size_t i = m_state.variables().numberOfArguments(); i--;)
m_state.variables().argument(i).clobberStructures();
for (size_t i = m_state.variables().numberOfLocals(); i--;)
m_state.variables().local(i).clobberStructures();
m_state.setHaveStructures(true);
m_state.setDidClobber(true);
}
template<typename AbstractStateType>
void AbstractInterpreter<AbstractStateType>::dump(PrintStream& out)
{
CommaPrinter comma(" ");
if (m_graph.m_form == SSA) {
HashSet<Node*>::iterator iter = m_state.block()->ssa->liveAtHead.begin();
HashSet<Node*>::iterator end = m_state.block()->ssa->liveAtHead.end();
for (; iter != end; ++iter) {
Node* node = *iter;
AbstractValue& value = forNode(node);
if (value.isClear())
continue;
out.print(comma, node, ":", value);
}
}
for (size_t i = 0; i < m_state.block()->size(); ++i) {
Node* node = m_state.block()->at(i);
AbstractValue& value = forNode(node);
if (value.isClear())
continue;
out.print(comma, node, ":", value);
}
}
template<typename AbstractStateType>
FiltrationResult AbstractInterpreter<AbstractStateType>::filter(
AbstractValue& value, const StructureSet& set)
{
if (value.filter(m_graph, set) == FiltrationOK)
return FiltrationOK;
m_state.setIsValid(false);
return Contradiction;
}
template<typename AbstractStateType>
FiltrationResult AbstractInterpreter<AbstractStateType>::filterArrayModes(
AbstractValue& value, ArrayModes arrayModes)
{
if (value.filterArrayModes(arrayModes) == FiltrationOK)
return FiltrationOK;
m_state.setIsValid(false);
return Contradiction;
}
template<typename AbstractStateType>
FiltrationResult AbstractInterpreter<AbstractStateType>::filter(
AbstractValue& value, SpeculatedType type)
{
if (value.filter(type) == FiltrationOK)
return FiltrationOK;
m_state.setIsValid(false);
return Contradiction;
}
template<typename AbstractStateType>
FiltrationResult AbstractInterpreter<AbstractStateType>::filterByValue(
AbstractValue& abstractValue, JSValue concreteValue)
{
if (abstractValue.filterByValue(concreteValue) == FiltrationOK)
return FiltrationOK;
m_state.setIsValid(false);
return Contradiction;
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)
#endif // DFGAbstractInterpreterInlines_h