Title: [91099] trunk/Source/_javascript_Core
Revision
91099
Author
[email protected]
Date
2011-07-15 13:14:27 -0700 (Fri, 15 Jul 2011)

Log Message

DFG JIT is inconsistent about fusing branches and speculating
integer comparisons for branches.
https://bugs.webkit.org/show_bug.cgi?id=64573

Patch by Filip Pizlo <[email protected]> on 2011-07-15
Reviewed by Gavin Barraclough.

This patch moves some of NonSpeculativeJIT's functionality up into the
JITCodeGenerator superclass so that it can be used from both JITs.  Now,
in cases where the speculative JIT doesn't want to speculate but still
wants to emit good code, it can reliably emit the same code sequence as
the non-speculative JIT.  This patch also extends the non-speculative
JIT's compare optimizations to include compare/branch fusing, and
extends the speculative JIT's compare optimizations to cover StrictEqual.

* dfg/DFGJITCodeGenerator.cpp:
(JSC::DFG::JITCodeGenerator::isKnownInteger):
(JSC::DFG::JITCodeGenerator::isKnownNumeric):
(JSC::DFG::JITCodeGenerator::nonSpeculativePeepholeBranch):
(JSC::DFG::JITCodeGenerator::nonSpeculativeCompare):
* dfg/DFGJITCodeGenerator.h:
(JSC::DFG::JITCodeGenerator::detectPeepHoleBranch):
* dfg/DFGNonSpeculativeJIT.cpp:
(JSC::DFG::NonSpeculativeJIT::compile):
* dfg/DFGNonSpeculativeJIT.h:
* dfg/DFGOperations.cpp:
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compare):
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT.h:
* wtf/Platform.h:

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (91098 => 91099)


--- trunk/Source/_javascript_Core/ChangeLog	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/ChangeLog	2011-07-15 20:14:27 UTC (rev 91099)
@@ -1,3 +1,36 @@
+2011-07-15  Filip Pizlo  <[email protected]>
+
+        DFG JIT is inconsistent about fusing branches and speculating
+        integer comparisons for branches.
+        https://bugs.webkit.org/show_bug.cgi?id=64573
+
+        Reviewed by Gavin Barraclough.
+        
+        This patch moves some of NonSpeculativeJIT's functionality up into the
+        JITCodeGenerator superclass so that it can be used from both JITs.  Now,
+        in cases where the speculative JIT doesn't want to speculate but still
+        wants to emit good code, it can reliably emit the same code sequence as
+        the non-speculative JIT.  This patch also extends the non-speculative
+        JIT's compare optimizations to include compare/branch fusing, and
+        extends the speculative JIT's compare optimizations to cover StrictEqual.
+
+        * dfg/DFGJITCodeGenerator.cpp:
+        (JSC::DFG::JITCodeGenerator::isKnownInteger):
+        (JSC::DFG::JITCodeGenerator::isKnownNumeric):
+        (JSC::DFG::JITCodeGenerator::nonSpeculativePeepholeBranch):
+        (JSC::DFG::JITCodeGenerator::nonSpeculativeCompare):
+        * dfg/DFGJITCodeGenerator.h:
+        (JSC::DFG::JITCodeGenerator::detectPeepHoleBranch):
+        * dfg/DFGNonSpeculativeJIT.cpp:
+        (JSC::DFG::NonSpeculativeJIT::compile):
+        * dfg/DFGNonSpeculativeJIT.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compare):
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT.h:
+        * wtf/Platform.h:
+
 2011-07-14  Gavin Barraclough  <[email protected]>
 
         https://bugs.webkit.org/show_bug.cgi?id=64250

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.cpp (91098 => 91099)


--- trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.cpp	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.cpp	2011-07-15 20:14:27 UTC (rev 91099)
@@ -325,6 +325,46 @@
     }
 }
 
+bool JITCodeGenerator::isKnownInteger(NodeIndex nodeIndex)
+{
+    if (isInt32Constant(nodeIndex))
+        return true;
+
+    GenerationInfo& info = m_generationInfo[m_jit.graph()[nodeIndex].virtualRegister()];
+
+    DataFormat registerFormat = info.registerFormat();
+    if (registerFormat != DataFormatNone)
+        return (registerFormat | DataFormatJS) == DataFormatJSInteger;
+
+    DataFormat spillFormat = info.spillFormat();
+    if (spillFormat != DataFormatNone)
+        return (spillFormat | DataFormatJS) == DataFormatJSInteger;
+
+    ASSERT(isConstant(nodeIndex));
+    return false;
+}
+
+bool JITCodeGenerator::isKnownNumeric(NodeIndex nodeIndex)
+{
+    if (isInt32Constant(nodeIndex) || isDoubleConstant(nodeIndex))
+        return true;
+
+    GenerationInfo& info = m_generationInfo[m_jit.graph()[nodeIndex].virtualRegister()];
+
+    DataFormat registerFormat = info.registerFormat();
+    if (registerFormat != DataFormatNone)
+        return (registerFormat | DataFormatJS) == DataFormatJSInteger
+            || (registerFormat | DataFormatJS) == DataFormatJSDouble;
+
+    DataFormat spillFormat = info.spillFormat();
+    if (spillFormat != DataFormatNone)
+        return (spillFormat | DataFormatJS) == DataFormatJSInteger
+            || (spillFormat | DataFormatJS) == DataFormatJSDouble;
+
+    ASSERT(isConstant(nodeIndex));
+    return false;
+}
+
 JITCompiler::Call JITCodeGenerator::cachedGetById(GPRReg baseGPR, GPRReg resultGPR, unsigned identifierNumber, JITCompiler::Jump slowPathTarget, NodeType nodeType)
 {
     GPRReg scratchGPR;
@@ -468,6 +508,116 @@
     m_jit.addMethodGet(slowCall, structToCompare, protoObj, protoStructToCompare, putFunction);
 }
 
+void JITCodeGenerator::nonSpeculativePeepholeBranch(Node& node, NodeIndex branchNodeIndex, MacroAssembler::RelationalCondition cond, Z_DFGOperation_EJJ helperFunction)
+{
+    Node& branchNode = m_jit.graph()[branchNodeIndex];
+    BlockIndex taken = m_jit.graph().blockIndexForBytecodeOffset(branchNode.takenBytecodeOffset());
+    BlockIndex notTaken = m_jit.graph().blockIndexForBytecodeOffset(branchNode.notTakenBytecodeOffset());
+
+    JITCompiler::ResultCondition callResultCondition = JITCompiler::NonZero;
+
+    // The branch instruction will branch to the taken block.
+    // If taken is next, switch taken with notTaken & invert the branch condition so we can fall through.
+    if (taken == (m_block + 1)) {
+        cond = JITCompiler::invert(cond);
+        callResultCondition = JITCompiler::Zero;
+        BlockIndex tmp = taken;
+        taken = notTaken;
+        notTaken = tmp;
+    }
+
+    JSValueOperand arg1(this, node.child1());
+    JSValueOperand arg2(this, node.child2());
+    GPRReg arg1GPR = arg1.gpr();
+    GPRReg arg2GPR = arg2.gpr();
+    
+    GPRTemporary result(this, arg2);
+    GPRReg resultGPR = result.gpr();
+    
+    JITCompiler::JumpList slowPath;
+    
+    if (!isKnownInteger(node.child1()))
+        slowPath.append(m_jit.branchPtr(MacroAssembler::Below, arg1GPR, GPRInfo::tagTypeNumberRegister));
+    if (!isKnownInteger(node.child2()))
+        slowPath.append(m_jit.branchPtr(MacroAssembler::Below, arg2GPR, GPRInfo::tagTypeNumberRegister));
+    
+    addBranch(m_jit.branch32(cond, arg1GPR, arg2GPR), taken);
+    
+    if (!isKnownInteger(node.child1()) || !isKnownInteger(node.child2())) {
+        addBranch(m_jit.jump(), notTaken);
+    
+        slowPath.link(&m_jit);
+    
+        silentSpillAllRegisters(resultGPR);
+        setupStubArguments(arg1GPR, arg2GPR);
+        m_jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+        appendCallWithExceptionCheck(helperFunction);
+        m_jit.move(GPRInfo::returnValueGPR, resultGPR);
+        silentFillAllRegisters(resultGPR);
+        
+        addBranch(m_jit.branchTest8(callResultCondition, resultGPR), taken);
+    }
+
+    if (notTaken != (m_block + 1))
+        addBranch(m_jit.jump(), notTaken);
+}
+
+bool JITCodeGenerator::nonSpeculativeCompare(Node& node, MacroAssembler::RelationalCondition cond, Z_DFGOperation_EJJ helperFunction)
+{
+    NodeIndex branchNodeIndex = detectPeepHoleBranch();
+    if (branchNodeIndex != NoNode) {
+        ASSERT(node.adjustedRefCount() == 1);
+        
+        nonSpeculativePeepholeBranch(node, branchNodeIndex, cond, helperFunction);
+    
+        use(node.child1());
+        use(node.child2());
+        m_compileIndex = branchNodeIndex;
+        
+        return true;
+    }
+        
+    JSValueOperand arg1(this, node.child1());
+    JSValueOperand arg2(this, node.child2());
+    GPRReg arg1GPR = arg1.gpr();
+    GPRReg arg2GPR = arg2.gpr();
+    
+    GPRTemporary result(this, arg2);
+    GPRReg resultGPR = result.gpr();
+    
+    JITCompiler::JumpList slowPath;
+    
+    if (!isKnownInteger(node.child1()))
+        slowPath.append(m_jit.branchPtr(MacroAssembler::Below, arg1GPR, GPRInfo::tagTypeNumberRegister));
+    if (!isKnownInteger(node.child2()))
+        slowPath.append(m_jit.branchPtr(MacroAssembler::Below, arg2GPR, GPRInfo::tagTypeNumberRegister));
+    
+    m_jit.compare32(cond, arg1GPR, arg2GPR, resultGPR);
+    
+    if (!isKnownInteger(node.child1()) || !isKnownInteger(node.child2())) {
+        JITCompiler::Jump haveResult = m_jit.jump();
+    
+        slowPath.link(&m_jit);
+        
+        silentSpillAllRegisters(resultGPR);
+        setupStubArguments(arg1GPR, arg2GPR);
+        m_jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+        appendCallWithExceptionCheck(helperFunction);
+        m_jit.move(GPRInfo::returnValueGPR, resultGPR);
+        silentFillAllRegisters(resultGPR);
+        
+        m_jit.andPtr(TrustedImm32(1), resultGPR);
+        
+        haveResult.link(&m_jit);
+    }
+    
+    m_jit.or32(TrustedImm32(ValueFalse), resultGPR);
+    
+    jsValueResult(resultGPR, m_compileIndex);
+    
+    return false;
+}
+
 void JITCodeGenerator::emitBranch(Node& node)
 {
     JSValueOperand value(this, node.child1());

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h (91098 => 91099)


--- trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h	2011-07-15 20:14:27 UTC (rev 91099)
@@ -412,6 +412,9 @@
         info.spill((DataFormat)(spillFormat | DataFormatJS));
     }
 
+    bool isKnownInteger(NodeIndex);
+    bool isKnownNumeric(NodeIndex);
+
     // Checks/accessors for constant values.
     bool isConstant(NodeIndex nodeIndex) { return m_jit.isConstant(nodeIndex); }
     bool isJSConstant(NodeIndex nodeIndex) { return m_jit.isJSConstant(nodeIndex); }
@@ -531,10 +534,29 @@
         }
     }
     
+    // Returns the node index of the branch node if peephole is okay, NoNode otherwise.
+    NodeIndex detectPeepHoleBranch()
+    {
+        NodeIndex lastNodeIndex = m_jit.graph().m_blocks[m_block]->end - 1;
+
+        // Check that no intervening nodes will be generated.
+        for (NodeIndex index = m_compileIndex + 1; index < lastNodeIndex; ++index) {
+            if (m_jit.graph()[index].shouldGenerate())
+                return NoNode;
+        }
+
+        // Check if the lastNode is a branch on this node.
+        Node& lastNode = m_jit.graph()[lastNodeIndex];
+        return lastNode.op == Branch && lastNode.child1() == m_compileIndex ? lastNodeIndex : NoNode;
+    }
+
     JITCompiler::Call cachedGetById(GPRReg baseGPR, GPRReg resultGPR, unsigned identifierNumber, JITCompiler::Jump slowPathTarget = JITCompiler::Jump(), NodeType = GetById);
     void cachedPutById(GPRReg baseGPR, GPRReg valueGPR, GPRReg scratchGPR, unsigned identifierNumber, PutKind, JITCompiler::Jump slowPathTarget = JITCompiler::Jump());
     void cachedGetMethod(GPRReg baseGPR, GPRReg resultGPR, unsigned identifierNumber, JITCompiler::Jump slowPathTarget = JITCompiler::Jump());
     
+    void nonSpeculativePeepholeBranch(Node&, NodeIndex branchNodeIndex, MacroAssembler::RelationalCondition, Z_DFGOperation_EJJ helperFunction);
+    bool nonSpeculativeCompare(Node&, MacroAssembler::RelationalCondition, Z_DFGOperation_EJJ helperFunction);
+    
     void emitBranch(Node&);
     
     MacroAssembler::Address addressOfCallData(int idx)

Modified: trunk/Source/_javascript_Core/dfg/DFGNonSpeculativeJIT.cpp (91098 => 91099)


--- trunk/Source/_javascript_Core/dfg/DFGNonSpeculativeJIT.cpp	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/dfg/DFGNonSpeculativeJIT.cpp	2011-07-15 20:14:27 UTC (rev 91099)
@@ -120,46 +120,6 @@
     truncatedToInteger.link(&m_jit);
 }
 
-bool NonSpeculativeJIT::isKnownInteger(NodeIndex nodeIndex)
-{
-    if (isInt32Constant(nodeIndex))
-        return true;
-
-    GenerationInfo& info = m_generationInfo[m_jit.graph()[nodeIndex].virtualRegister()];
-
-    DataFormat registerFormat = info.registerFormat();
-    if (registerFormat != DataFormatNone)
-        return (registerFormat | DataFormatJS) == DataFormatJSInteger;
-
-    DataFormat spillFormat = info.spillFormat();
-    if (spillFormat != DataFormatNone)
-        return (spillFormat | DataFormatJS) == DataFormatJSInteger;
-
-    ASSERT(isConstant(nodeIndex));
-    return false;
-}
-
-bool NonSpeculativeJIT::isKnownNumeric(NodeIndex nodeIndex)
-{
-    if (isInt32Constant(nodeIndex) || isDoubleConstant(nodeIndex))
-        return true;
-
-    GenerationInfo& info = m_generationInfo[m_jit.graph()[nodeIndex].virtualRegister()];
-
-    DataFormat registerFormat = info.registerFormat();
-    if (registerFormat != DataFormatNone)
-        return (registerFormat | DataFormatJS) == DataFormatJSInteger
-            || (registerFormat | DataFormatJS) == DataFormatJSDouble;
-
-    DataFormat spillFormat = info.spillFormat();
-    if (spillFormat != DataFormatNone)
-        return (spillFormat | DataFormatJS) == DataFormatJSInteger
-            || (spillFormat | DataFormatJS) == DataFormatJSDouble;
-
-    ASSERT(isConstant(nodeIndex));
-    return false;
-}
-
 void NonSpeculativeJIT::knownConstantArithOp(NodeType op, NodeIndex regChild, NodeIndex immChild, bool commute)
 {
     JSValueOperand regArg(this, regChild);
@@ -342,46 +302,6 @@
     jsValueResult(resultGPR, m_compileIndex);
 }
 
-void NonSpeculativeJIT::compare(Node& node, MacroAssembler::RelationalCondition cond, Z_DFGOperation_EJJ helperFunction)
-{
-    // FIXME: should do some peephole to fuse compare/branch
-        
-    JSValueOperand arg1(this, node.child1());
-    JSValueOperand arg2(this, node.child2());
-    GPRReg arg1GPR = arg1.gpr();
-    GPRReg arg2GPR = arg2.gpr();
-    
-    GPRTemporary result(this, arg2);
-    GPRReg resultGPR = result.gpr();
-    
-    JITCompiler::JumpList slowPath;
-    
-    if (!isKnownInteger(node.child1()))
-        slowPath.append(m_jit.branchPtr(MacroAssembler::Below, arg1GPR, GPRInfo::tagTypeNumberRegister));
-    if (!isKnownInteger(node.child2()))
-        slowPath.append(m_jit.branchPtr(MacroAssembler::Below, arg2GPR, GPRInfo::tagTypeNumberRegister));
-    
-    m_jit.compare32(cond, arg1GPR, arg2GPR, resultGPR);
-    
-    JITCompiler::Jump haveResult = m_jit.jump();
-    
-    slowPath.link(&m_jit);
-    
-    silentSpillAllRegisters(resultGPR);
-    setupStubArguments(arg1GPR, arg2GPR);
-    m_jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
-    appendCallWithExceptionCheck(helperFunction);
-    m_jit.move(GPRInfo::returnValueGPR, resultGPR);
-    silentFillAllRegisters(resultGPR);
-        
-    m_jit.andPtr(TrustedImm32(static_cast<int32_t>(1)), resultGPR);
-    
-    haveResult.link(&m_jit);
-    
-    m_jit.or32(TrustedImm32(ValueFalse), resultGPR);
-    jsValueResult(resultGPR, m_compileIndex);
-}
-
 void NonSpeculativeJIT::compile(SpeculationCheckIndexIterator& checkIterator, Node& node)
 {
     // Check for speculation checks from the corresponding instruction in the
@@ -733,27 +653,33 @@
     }
 
     case CompareLess:
-        compare(node, MacroAssembler::LessThan, operationCompareLess);
+        if (nonSpeculativeCompare(node, MacroAssembler::LessThan, operationCompareLess))
+            return;
         break;
         
     case CompareLessEq:
-        compare(node, MacroAssembler::LessThanOrEqual, operationCompareLessEq);
+        if (nonSpeculativeCompare(node, MacroAssembler::LessThanOrEqual, operationCompareLessEq))
+            return;
         break;
         
     case CompareGreater:
-        compare(node, MacroAssembler::GreaterThan, operationCompareGreater);
+        if (nonSpeculativeCompare(node, MacroAssembler::GreaterThan, operationCompareGreater))
+            return;
         break;
         
     case CompareGreaterEq:
-        compare(node, MacroAssembler::GreaterThanOrEqual, operationCompareGreaterEq);
+        if (nonSpeculativeCompare(node, MacroAssembler::GreaterThanOrEqual, operationCompareGreaterEq))
+            return;
         break;
         
     case CompareEq:
-        compare(node, MacroAssembler::Equal, operationCompareEq);
+        if (nonSpeculativeCompare(node, MacroAssembler::Equal, operationCompareEq))
+            return;
         break;
 
     case CompareStrictEq:
-        compare(node, MacroAssembler::Equal, operationCompareStrictEq);
+        if (nonSpeculativeCompare(node, MacroAssembler::Equal, operationCompareStrictEq))
+            return;
         break;
 
     case GetByVal: {

Modified: trunk/Source/_javascript_Core/dfg/DFGNonSpeculativeJIT.h (91098 => 91099)


--- trunk/Source/_javascript_Core/dfg/DFGNonSpeculativeJIT.h	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/dfg/DFGNonSpeculativeJIT.h	2011-07-15 20:14:27 UTC (rev 91099)
@@ -82,9 +82,6 @@
     void compile(SpeculationCheckIndexIterator&, Node&);
     void compile(SpeculationCheckIndexIterator&, BasicBlock&);
 
-    bool isKnownInteger(NodeIndex);
-    bool isKnownNumeric(NodeIndex);
-
     // These methods are used to plant calls out to C++
     // helper routines to convert between types.
     void valueToNumber(JSValueOperand&, GPRReg result);
@@ -102,7 +99,6 @@
     // internal helpers for add/sub/mul operations
     void knownConstantArithOp(NodeType op, NodeIndex regChild, NodeIndex immChild, bool commute);
     void basicArithOp(NodeType op, Node&);
-    void compare(Node&, MacroAssembler::RelationalCondition, Z_DFGOperation_EJJ);
 
     EntryLocationVector m_entryLocations;
 };

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp (91098 => 91099)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2011-07-15 20:14:27 UTC (rev 91099)
@@ -424,7 +424,7 @@
         if (shouldSpeculateInteger(node.child1(), node.child2()))
             compilePeepHoleIntegerBranch(node, branchNodeIndex, condition);
         else
-            compilePeepHoleCall(node, branchNodeIndex, operation);
+            nonSpeculativePeepholeBranch(node, branchNodeIndex, condition, operation);
 
         use(node.child1());
         use(node.child2());
@@ -797,18 +797,10 @@
             return;
         break;
 
-    case CompareStrictEq: {
-        SpeculateIntegerOperand op1(this, node.child1());
-        SpeculateIntegerOperand op2(this, node.child2());
-        GPRTemporary result(this, op1, op2);
-
-        m_jit.compare32(JITCompiler::Equal, op1.gpr(), op2.gpr(), result.gpr());
-
-        // If we add a DataFormatBool, we should use it here.
-        m_jit.or32(TrustedImm32(ValueFalse), result.gpr());
-        jsValueResult(result.gpr(), m_compileIndex);
+    case CompareStrictEq:
+        if (compare(node, JITCompiler::Equal, operationCompareStrictEq))
+            return;
         break;
-    }
 
     case GetByVal: {
         NodeIndex alias = node.child3();

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.h (91098 => 91099)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.h	2011-07-15 20:10:07 UTC (rev 91098)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.h	2011-07-15 20:14:27 UTC (rev 91099)
@@ -139,22 +139,6 @@
     void checkArgumentTypes();
     void initializeVariableTypes();
 
-    // Returns the node index of the branch node if peephole is okay, NoNode otherwise.
-    NodeIndex detectPeepHoleBranch()
-    {
-        NodeIndex lastNodeIndex = m_jit.graph().m_blocks[m_block]->end - 1;
-
-        // Check that no intervening nodes will be generated.
-        for (NodeIndex index = m_compileIndex + 1; index < lastNodeIndex; ++index) {
-            if (m_jit.graph()[index].shouldGenerate())
-                return NoNode;
-        }
-
-        // Check if the lastNode is a branch on this node.
-        Node& lastNode = m_jit.graph()[lastNodeIndex];
-        return lastNode.op == Branch && lastNode.child1() == m_compileIndex ? lastNodeIndex : NoNode;
-    }
-
     bool isInteger(NodeIndex nodeIndex)
     {
         Node& node = m_jit.graph()[nodeIndex];
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to