Changeset 182498 in webkit


Ignore:
Timestamp:
Apr 7, 2015 3:09:15 PM (9 years ago)
Author:
fpizlo@apple.com
Message:

Constant folding of typed array properties should be handled by AI rather than strength reduction
https://bugs.webkit.org/show_bug.cgi?id=143496

Reviewed by Geoffrey Garen.

Handling constant folding in AI is better because it precludes us from having to fixpoint the CFA
phase and whatever other phase did the folding in order to find all constants.

This also removes the TypedArrayWatchpoint node type because we can just set the watchpoint
directly.

This also fixes a bug in FTL lowering of GetTypedArrayByteOffset. The bug was previously not
found because all of the tests for it involved the property getting constant folded. I found that
the codegen was bad because an earlier version of the patch broke that constant folding. This
adds a new test for that node type, which makes constant folding impossible by allocating a new
typed array every type. The lesson here is: if you write a test for something, run the test with
full IR dumps to make sure it's actually testing the thing you want it to test.

  • dfg/DFGAbstractInterpreterInlines.h:

(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):

  • dfg/DFGClobberize.h:

(JSC::DFG::clobberize):

  • dfg/DFGConstantFoldingPhase.cpp:

(JSC::DFG::ConstantFoldingPhase::foldConstants):

  • dfg/DFGDoesGC.cpp:

(JSC::DFG::doesGC):

  • dfg/DFGFixupPhase.cpp:

(JSC::DFG::FixupPhase::fixupNode):

  • dfg/DFGGraph.cpp:

(JSC::DFG::Graph::dump):
(JSC::DFG::Graph::tryGetFoldableView):
(JSC::DFG::Graph::tryGetFoldableViewForChild1): Deleted.

  • dfg/DFGGraph.h:
  • dfg/DFGNode.h:

(JSC::DFG::Node::hasTypedArray): Deleted.
(JSC::DFG::Node::typedArray): Deleted.

  • dfg/DFGNodeType.h:
  • dfg/DFGPredictionPropagationPhase.cpp:

(JSC::DFG::PredictionPropagationPhase::propagate):

  • dfg/DFGSafeToExecute.h:

(JSC::DFG::safeToExecute):

  • dfg/DFGSpeculativeJIT.cpp:

(JSC::DFG::SpeculativeJIT::jumpForTypedArrayOutOfBounds):

  • dfg/DFGSpeculativeJIT32_64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGSpeculativeJIT64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGStrengthReductionPhase.cpp:

(JSC::DFG::StrengthReductionPhase::handleNode):
(JSC::DFG::StrengthReductionPhase::foldTypedArrayPropertyToConstant): Deleted.
(JSC::DFG::StrengthReductionPhase::prepareToFoldTypedArray): Deleted.

  • dfg/DFGWatchpointCollectionPhase.cpp:

(JSC::DFG::WatchpointCollectionPhase::handle):
(JSC::DFG::WatchpointCollectionPhase::addLazily):

  • ftl/FTLCapabilities.cpp:

(JSC::FTL::canCompile):

  • ftl/FTLLowerDFGToLLVM.cpp:

(JSC::FTL::LowerDFGToLLVM::compileNode):
(JSC::FTL::LowerDFGToLLVM::compileGetTypedArrayByteOffset):
(JSC::FTL::LowerDFGToLLVM::typedArrayLength):

  • tests/stress/fold-typed-array-properties.js:

(foo):

  • tests/stress/typed-array-byte-offset.js: Added.

(foo):

Location:
trunk/Source/JavaScriptCore
Files:
1 added
20 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r182495 r182498  
     12015-04-07  Filip Pizlo  <fpizlo@apple.com>
     2
     3        Constant folding of typed array properties should be handled by AI rather than strength reduction
     4        https://bugs.webkit.org/show_bug.cgi?id=143496
     5
     6        Reviewed by Geoffrey Garen.
     7       
     8        Handling constant folding in AI is better because it precludes us from having to fixpoint the CFA
     9        phase and whatever other phase did the folding in order to find all constants.
     10       
     11        This also removes the TypedArrayWatchpoint node type because we can just set the watchpoint
     12        directly.
     13       
     14        This also fixes a bug in FTL lowering of GetTypedArrayByteOffset. The bug was previously not
     15        found because all of the tests for it involved the property getting constant folded. I found that
     16        the codegen was bad because an earlier version of the patch broke that constant folding. This
     17        adds a new test for that node type, which makes constant folding impossible by allocating a new
     18        typed array every type. The lesson here is: if you write a test for something, run the test with
     19        full IR dumps to make sure it's actually testing the thing you want it to test.
     20
     21        * dfg/DFGAbstractInterpreterInlines.h:
     22        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
     23        * dfg/DFGClobberize.h:
     24        (JSC::DFG::clobberize):
     25        * dfg/DFGConstantFoldingPhase.cpp:
     26        (JSC::DFG::ConstantFoldingPhase::foldConstants):
     27        * dfg/DFGDoesGC.cpp:
     28        (JSC::DFG::doesGC):
     29        * dfg/DFGFixupPhase.cpp:
     30        (JSC::DFG::FixupPhase::fixupNode):
     31        * dfg/DFGGraph.cpp:
     32        (JSC::DFG::Graph::dump):
     33        (JSC::DFG::Graph::tryGetFoldableView):
     34        (JSC::DFG::Graph::tryGetFoldableViewForChild1): Deleted.
     35        * dfg/DFGGraph.h:
     36        * dfg/DFGNode.h:
     37        (JSC::DFG::Node::hasTypedArray): Deleted.
     38        (JSC::DFG::Node::typedArray): Deleted.
     39        * dfg/DFGNodeType.h:
     40        * dfg/DFGPredictionPropagationPhase.cpp:
     41        (JSC::DFG::PredictionPropagationPhase::propagate):
     42        * dfg/DFGSafeToExecute.h:
     43        (JSC::DFG::safeToExecute):
     44        * dfg/DFGSpeculativeJIT.cpp:
     45        (JSC::DFG::SpeculativeJIT::jumpForTypedArrayOutOfBounds):
     46        * dfg/DFGSpeculativeJIT32_64.cpp:
     47        (JSC::DFG::SpeculativeJIT::compile):
     48        * dfg/DFGSpeculativeJIT64.cpp:
     49        (JSC::DFG::SpeculativeJIT::compile):
     50        * dfg/DFGStrengthReductionPhase.cpp:
     51        (JSC::DFG::StrengthReductionPhase::handleNode):
     52        (JSC::DFG::StrengthReductionPhase::foldTypedArrayPropertyToConstant): Deleted.
     53        (JSC::DFG::StrengthReductionPhase::prepareToFoldTypedArray): Deleted.
     54        * dfg/DFGWatchpointCollectionPhase.cpp:
     55        (JSC::DFG::WatchpointCollectionPhase::handle):
     56        (JSC::DFG::WatchpointCollectionPhase::addLazily):
     57        * ftl/FTLCapabilities.cpp:
     58        (JSC::FTL::canCompile):
     59        * ftl/FTLLowerDFGToLLVM.cpp:
     60        (JSC::FTL::LowerDFGToLLVM::compileNode):
     61        (JSC::FTL::LowerDFGToLLVM::compileGetTypedArrayByteOffset):
     62        (JSC::FTL::LowerDFGToLLVM::typedArrayLength):
     63        * tests/stress/fold-typed-array-properties.js:
     64        (foo):
     65        * tests/stress/typed-array-byte-offset.js: Added.
     66        (foo):
     67
    1682015-04-07  Matthew Mirman  <mmirman@apple.com>
    269
  • trunk/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h

    r182433 r182498  
    13841384        break;
    13851385       
    1386     case TypedArrayWatchpoint:
    1387         break;
    1388    
    13891386    case CreateDirectArguments:
    13901387        forNode(node).set(m_graph, m_codeBlock->globalObjectFor(node->origin.semantic)->directArgumentsStructure());
     
    15211518    }
    15221519           
    1523     case GetArrayLength:
     1520    case GetArrayLength: {
     1521        JSArrayBufferView* view = m_graph.tryGetFoldableView(
     1522            forNode(node->child1()).m_value, node->arrayMode());
     1523        if (view) {
     1524            setConstant(node, jsNumber(view->length()));
     1525            break;
     1526        }
    15241527        forNode(node).setType(SpecInt32);
    15251528        break;
     1529    }
    15261530       
    15271531    case CheckStructure: {
     
    17061710        break;
    17071711    }
    1708     case GetIndexedPropertyStorage:
     1712    case GetIndexedPropertyStorage: {
     1713        JSArrayBufferView* view = m_graph.tryGetFoldableView(
     1714            forNode(node->child1()).m_value, node->arrayMode());
     1715        if (view)
     1716            m_state.setFoundConstants(true);
     1717        forNode(node).clear();
     1718        break;
     1719    }
    17091720    case ConstantStoragePointer: {
    17101721        forNode(node).clear();
     
    17131724       
    17141725    case GetTypedArrayByteOffset: {
     1726        JSArrayBufferView* view = m_graph.tryGetFoldableView(forNode(node->child1()).m_value);
     1727        if (view) {
     1728            setConstant(node, jsNumber(view->byteOffset()));
     1729            break;
     1730        }
    17151731        forNode(node).setType(SpecInt32);
    17161732        break;
  • trunk/Source/JavaScriptCore/dfg/DFGClobberize.h

    r182433 r182498  
    307307        return;
    308308
    309     case TypedArrayWatchpoint:
    310         read(Watchpoint_fire);
    311         write(SideState);
    312         return;
    313        
    314309    case NotifyWrite:
    315310        write(Watchpoint_fire);
  • trunk/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp

    r182004 r182498  
    116116            }
    117117               
     118            case GetIndexedPropertyStorage: {
     119                JSArrayBufferView* view = m_graph.tryGetFoldableView(
     120                    m_state.forNode(node->child1()).m_value, node->arrayMode());
     121                if (!view)
     122                    break;
     123               
     124                if (view->mode() == FastTypedArray) {
     125                    // FIXME: It would be awesome to be able to fold the property storage for
     126                    // these GC-allocated typed arrays. For now it doesn't matter because the
     127                    // most common use-cases for constant typed arrays involve large arrays with
     128                    // aliased buffer views.
     129                    // https://bugs.webkit.org/show_bug.cgi?id=125425
     130                    break;
     131                }
     132               
     133                m_interpreter.execute(indexInBlock);
     134                eliminated = true;
     135               
     136                m_insertionSet.insertNode(
     137                    indexInBlock, SpecNone, Phantom, node->origin, node->children);
     138                node->convertToConstantStoragePointer(view->vector());
     139                break;
     140            }
     141               
    118142            case CheckStructureImmediate: {
    119143                AbstractValue& value = m_state.forNode(node->child1());
  • trunk/Source/JavaScriptCore/dfg/DFGDoesGC.cpp

    r182433 r182498  
    165165    case InvalidationPoint:
    166166    case NotifyWrite:
    167     case TypedArrayWatchpoint:
    168167    case CheckInBounds:
    169168    case ConstantStoragePointer:
  • trunk/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp

    r182433 r182498  
    12391239        case StoreBarrier:
    12401240        case StoreBarrierWithNullCheck:
    1241         case TypedArrayWatchpoint:
    12421241        case MovHint:
    12431242        case ZombieHint:
  • trunk/Source/JavaScriptCore/dfg/DFGGraph.cpp

    r182167 r182498  
    314314    if (node->hasVariableWatchpointSet())
    315315        out.print(comma, RawPointer(node->variableWatchpointSet()));
    316     if (node->hasTypedArray())
    317         out.print(comma, inContext(JSValue(node->typedArray()), context));
    318316    if (node->hasStoragePointer())
    319317        out.print(comma, RawPointer(node->storagePointer()));
     
    10721070}
    10731071
    1074 JSArrayBufferView* Graph::tryGetFoldableView(Node* node)
    1075 {
    1076     JSArrayBufferView* view = node->dynamicCastConstant<JSArrayBufferView*>();
    1077     if (!view)
     1072JSArrayBufferView* Graph::tryGetFoldableView(JSValue value)
     1073{
     1074    if (!value)
     1075        return nullptr;
     1076    JSArrayBufferView* view = jsDynamicCast<JSArrayBufferView*>(value);
     1077    if (!value)
    10781078        return nullptr;
    10791079    if (!view->length())
    10801080        return nullptr;
    10811081    WTF::loadLoadFence();
     1082    watchpoints().addLazily(view);
    10821083    return view;
    10831084}
    10841085
    1085 JSArrayBufferView* Graph::tryGetFoldableView(Node* node, ArrayMode arrayMode)
     1086JSArrayBufferView* Graph::tryGetFoldableView(JSValue value, ArrayMode arrayMode)
    10861087{
    10871088    if (arrayMode.typedArrayType() == NotTypedArray)
    1088         return 0;
    1089     return tryGetFoldableView(node);
    1090 }
    1091 
    1092 JSArrayBufferView* Graph::tryGetFoldableViewForChild1(Node* node)
    1093 {
    1094     return tryGetFoldableView(child(node, 0).node(), node->arrayMode());
     1089        return nullptr;
     1090    return tryGetFoldableView(value);
    10951091}
    10961092
  • trunk/Source/JavaScriptCore/dfg/DFGGraph.h

    r182167 r182498  
    693693    JSValue tryGetConstantClosureVar(Node*, ScopeOffset);
    694694   
    695     JSArrayBufferView* tryGetFoldableView(Node*);
    696     JSArrayBufferView* tryGetFoldableView(Node*, ArrayMode);
    697     JSArrayBufferView* tryGetFoldableViewForChild1(Node*);
     695    JSArrayBufferView* tryGetFoldableView(JSValue);
     696    JSArrayBufferView* tryGetFoldableView(JSValue, ArrayMode arrayMode);
    698697   
    699698    void registerFrozenValues();
  • trunk/Source/JavaScriptCore/dfg/DFGNode.h

    r181993 r182498  
    12301230    }
    12311231   
    1232     bool hasTypedArray()
    1233     {
    1234         return op() == TypedArrayWatchpoint;
    1235     }
    1236    
    1237     JSArrayBufferView* typedArray()
    1238     {
    1239         return reinterpret_cast<JSArrayBufferView*>(m_opInfo);
    1240     }
    1241    
    12421232    bool hasStoragePointer()
    12431233    {
  • trunk/Source/JavaScriptCore/dfg/DFGNodeType.h

    r182433 r182498  
    175175    macro(GetIndexedPropertyStorage, NodeResultStorage) \
    176176    macro(ConstantStoragePointer, NodeResultStorage) \
    177     macro(TypedArrayWatchpoint, NodeMustGenerate) \
    178177    macro(GetGetter, NodeResultJS) \
    179178    macro(GetSetter, NodeResultJS) \
  • trunk/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp

    r182433 r182498  
    646646        case LoopHint:
    647647        case NotifyWrite:
    648         case TypedArrayWatchpoint:
    649648        case ConstantStoragePointer:
    650649        case MovHint:
  • trunk/Source/JavaScriptCore/dfg/DFGSafeToExecute.h

    r182433 r182498  
    252252    case InvalidationPoint:
    253253    case NotifyWrite:
    254     case TypedArrayWatchpoint:
    255254    case CheckInBounds:
    256255    case ConstantStoragePointer:
  • trunk/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp

    r182433 r182498  
    22692269    if (node->op() == PutByValAlias)
    22702270        return JITCompiler::Jump();
    2271     if (JSArrayBufferView* view = m_jit.graph().tryGetFoldableViewForChild1(node)) {
     2271    JSArrayBufferView* view = m_jit.graph().tryGetFoldableView(
     2272        m_state.forNode(m_jit.graph().child(node, 0)).m_value, node->arrayMode());
     2273    if (view) {
    22722274        uint32_t length = view->length();
    22732275        Node* indexNode = m_jit.graph().child(node, 1).node();
  • trunk/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp

    r182433 r182498  
    35343534    }
    35353535
    3536     case AllocationProfileWatchpoint:
    3537     case TypedArrayWatchpoint: {
     3536    case AllocationProfileWatchpoint: {
    35383537        noResult(node);
    35393538        break;
  • trunk/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp

    r182444 r182498  
    36093609    }
    36103610       
    3611     case AllocationProfileWatchpoint:
    3612     case TypedArrayWatchpoint: {
     3611    case AllocationProfileWatchpoint: {
    36133612        noResult(node);
    36143613        break;
  • trunk/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp

    r181993 r182498  
    147147            break;
    148148
    149         case GetArrayLength:
    150             if (JSArrayBufferView* view = m_graph.tryGetFoldableViewForChild1(m_node))
    151                 foldTypedArrayPropertyToConstant(view, jsNumber(view->length()));
    152             break;
    153            
    154         case GetTypedArrayByteOffset:
    155             if (JSArrayBufferView* view = m_graph.tryGetFoldableView(m_node->child1().node()))
    156                 foldTypedArrayPropertyToConstant(view, jsNumber(view->byteOffset()));
    157             break;
    158            
    159         case GetIndexedPropertyStorage:
    160             if (JSArrayBufferView* view = m_graph.tryGetFoldableViewForChild1(m_node)) {
    161                 if (view->mode() != FastTypedArray) {
    162                     prepareToFoldTypedArray(view);
    163                     m_node->convertToConstantStoragePointer(view->vector());
    164                     m_changed = true;
    165                     break;
    166                 } else {
    167                     // FIXME: It would be awesome to be able to fold the property storage for
    168                     // these GC-allocated typed arrays. For now it doesn't matter because the
    169                     // most common use-cases for constant typed arrays involve large arrays with
    170                     // aliased buffer views.
    171                     // https://bugs.webkit.org/show_bug.cgi?id=125425
    172                 }
    173             }
    174             break;
    175            
    176149        case ValueRep:
    177150        case Int52Rep:
     
    284257    }
    285258   
    286     void foldTypedArrayPropertyToConstant(JSArrayBufferView* view, JSValue constant)
    287     {
    288         prepareToFoldTypedArray(view);
    289         m_graph.convertToConstant(m_node, constant);
    290         m_changed = true;
    291     }
    292    
    293     void prepareToFoldTypedArray(JSArrayBufferView* view)
    294     {
    295         m_insertionSet.insertNode(
    296             m_nodeIndex, SpecNone, TypedArrayWatchpoint, m_node->origin,
    297             OpInfo(view));
    298         m_insertionSet.insertNode(
    299             m_nodeIndex, SpecNone, Phantom, m_node->origin, m_node->children);
    300     }
    301    
    302259    void handleCommutativity()
    303260    {
  • trunk/Source/JavaScriptCore/dfg/DFGWatchpointCollectionPhase.cpp

    r180993 r182498  
    9393            if (m_node->arrayMode().type() == Array::String)
    9494                handleStringGetByVal();
    95 
    96             if (JSArrayBufferView* view = m_graph.tryGetFoldableViewForChild1(m_node))
    97                 addLazily(view);
    98             break;
    99            
    100         case PutByVal:
    101             if (JSArrayBufferView* view = m_graph.tryGetFoldableViewForChild1(m_node))
    102                 addLazily(view);
    10395            break;
    10496           
     
    120112        case VarInjectionWatchpoint:
    121113            addLazily(globalObject()->varInjectionWatchpoint());
    122             break;
    123            
    124         case TypedArrayWatchpoint:
    125             addLazily(m_node->typedArray());
    126114            break;
    127115           
     
    155143        m_graph.watchpoints().addLazily(set);
    156144    }
    157     void addLazily(JSArrayBufferView* view)
    158     {
    159         m_graph.watchpoints().addLazily(view);
    160     }
    161145   
    162146    JSGlobalObject* globalObject()
  • trunk/Source/JavaScriptCore/ftl/FTLCapabilities.cpp

    r182433 r182498  
    120120    case AllocatePropertyStorage:
    121121    case ReallocatePropertyStorage:
    122     case TypedArrayWatchpoint:
    123122    case GetTypedArrayByteOffset:
    124123    case NotifyWrite:
  • trunk/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp

    r182444 r182498  
    847847        case PhantomLocal:
    848848        case LoopHint:
    849         case TypedArrayWatchpoint:
    850849        case AllocationProfileWatchpoint:
    851850        case MovHint:
     
    21062105        LBasicBlock continuation = FTL_NEW_BLOCK(m_out, ("continuation branch"));
    21072106       
    2108         LValue baseAddress = m_out.addPtr(basePtr, JSArrayBufferView::offsetOfMode());
     2107        LValue mode = m_out.load32(basePtr, m_heaps.JSArrayBufferView_mode);
    21092108        m_out.branch(
    2110             m_out.notEqual(baseAddress , m_out.constIntPtr(WastefulTypedArray)),
     2109            m_out.notEqual(mode, m_out.constInt32(WastefulTypedArray)),
    21112110            unsure(simpleCase), unsure(wastefulCase));
    21122111
     
    21262125        LValue dataPtr = m_out.loadPtr(arrayBufferPtr, m_heaps.ArrayBuffer_data);
    21272126
    2128         ValueFromBlock wastefulOut = m_out.anchor(m_out.sub(dataPtr, vectorPtr));       
     2127        ValueFromBlock wastefulOut = m_out.anchor(m_out.sub(vectorPtr, dataPtr));
    21292128
    21302129        m_out.jump(continuation);
     
    57865785    LValue typedArrayLength(Edge baseEdge, ArrayMode arrayMode, LValue base)
    57875786    {
    5788         if (JSArrayBufferView* view = m_graph.tryGetFoldableView(baseEdge.node(), arrayMode))
     5787        JSArrayBufferView* view = m_graph.tryGetFoldableView(
     5788            m_state.forNode(baseEdge).m_value, arrayMode);
     5789        if (view)
    57895790            return m_out.constInt32(view->length());
    57905791        return m_out.load32NonNegative(base, m_heaps.JSArrayBufferView_length);
  • trunk/Source/JavaScriptCore/tests/stress/fold-typed-array-properties.js

    r160292 r182498  
    22
    33if (a.length != 1)
    4     throw "Error: bad length: " + a.length;
     4    throw "Error: bad length (start): " + a.length;
    55if (a.byteOffset != 4)
    6     throw "Error: bad offset: " + a.byteOffset;
     6    throw "Error: bad offset (start): " + a.byteOffset;
    77if (a.byteLength != 4)
    8     throw "Error: bad byte length: " + a.byteLength;
     8    throw "Error: bad byte length (start): " + a.byteLength;
    99
    10 function foo() {
    11     if (a.length != 1)
    12         throw "Error: bad length: " + a.length;
    13     if (a.byteOffset != 4)
    14         throw "Error: bad offset: " + a.byteOffset;
    15     if (a.byteLength != 4)
    16         throw "Error: bad byte length: " + a.byteLength;
     10function foo(when) {
     11    var tmp = a.length;
     12    if (tmp != 1)
     13        throw "Error: bad length (" + when + "): " + tmp;
     14    tmp = a.byteOffset;
     15    if (tmp != 4)
     16        throw "Error: bad offset (" + when + "): " + tmp;
     17    tmp = a.byteLength;
     18    if (tmp != 4)
     19        throw "Error: bad byte length (" + when + "): " + tmp;
    1720}
    1821
    1922for (var i = 0; i < 1000000; ++i)
    20     foo();
     23    foo("loop");
    2124
    2225transferArrayBuffer(a.buffer);
     
    2427var didThrow = false;
    2528try {
    26     foo();
     29    foo("after transfer");
    2730} catch (e) {
    2831    didThrow = true;
     
    3336
    3437if (a.length != 0)
    35     throw "Error: bad length: " + a.length;
     38    throw "Error: bad length (end): " + a.length;
    3639if (a.byteOffset != 0)
    37     throw "Error: bad offset: " + a.byteOffset;
     40    throw "Error: bad offset (end): " + a.byteOffset;
    3841if (a.byteLength != 0)
    39     throw "Error: bad byte length: " + a.byteLength;
     42    throw "Error: bad byte length (end): " + a.byteLength;
Note: See TracChangeset for help on using the changeset viewer.