[webkit-dev] Question about javascriptcore garbage collection

Krzysztof Kowalczyk kkowalczyk at gmail.com
Sat Sep 3 02:20:03 PDT 2005


I've been working on Windows port of JavaScriptCore. The code works
but I've been seeing crashes in several tests (when I run regression
suite WebKitTools\Scripts\run-javascriptcore-tests). I finally tracked
down the problem and it's quite weird so I was wondering if someone
could confirm behaviour on Mac.

All values deriving from AllocatedValueImp in jsc are mostly allocated
from a pool of CollectorCell values. During garbage collection each
value on the stack is checked and if it points exactly at the
beginning of a CollectorCell in any of CollectorBlock's
(IS_CELL_ALIGNED(x) must be true), it is marked as used (by casting
the pointer to CollectorCell to AllocatedValueImp and calling mark()
on it).

After this mark step is done, all non-marked values are considered
free and reclaimed in the 'sweep' stage.

The crashes I've seen are because objects were accessed after beeing
freed i.e. they were not marked during. The root cause of the problem
is that sometimes the values returned from allocation rutines (and
therefore put on the stack) are not the address of the cell, but the
address of the cell+4. I assume this is due to how compiler (Visual
Studio 2003 in my case) implements object layout in face of
inheritance.

I assume those crashes happen on win and not on mac (otherwise I would
expect similar crashes happen on mac) because of differences between
Visual C 2003 and gcc.

So I have 2 questions:
* could someone verify that this doesn't happen on mac
* could someone shed some light on the subject. Does webkit's GC
depend on gcc's undocumented behaviour or is vc violating C++ spec?
* what would be a good way of fixing it? I've fixed the crashes with
this small change to the kjs\Collector.cpp:
cvs diff -u kjs\collector.cpp:

@@ -251,8 +287,10 @@
 
   while (p != e) {
     char *x = *p++;
-    if (IS_CELL_ALIGNED(x) && x) {
+    if (x && (IS_CELL_ALIGNED(x) || IS_CELL_ALIGNED(x-4))) {
       for (int block = 0; block < usedBlocks; block++) {
+        if (IS_CELL_ALIGNED(x-4))
+            x -= 4;
         size_t offset = x - reinterpret_cast<char *>(blocks[block]);
         if (offset <= lastCellOffset && offset % sizeof(CollectorCell) == 0)
           goto gotGoodPointer;

but it doesn't look like a great solution (relying too much on
internals of C++ compiler).

And here's a specific example of this that shows the problem. 
- set a breakpoint at TypeOfNode::evaluate() where s = "number".
- run ecma/Date/15.9.3.1-1.js test
- first breakpoint hit should be:
0:000> kb
ChildEBP RetAddr  Args to Child              
0012f200 0044370e 0012f3f8 003460d0 00000000
testkjs!KJS::TypeOfNode::evaluate+0x17e
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
1080]
0012f23c 00443cd8 0012f3f8 0034c29c 0034c298
testkjs!KJS::EqualNode::evaluate+0x1e
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
1429]
0012f268 00443c2e 0012f3f8 0012f284 00412c73
testkjs!KJS::BinaryLogicalNode::evaluate+0xc8
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
1516]
0012f294 004465f4 0012f3f8 00412a6f 0012f2e8
testkjs!KJS::BinaryLogicalNode::evaluate+0x1e
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
1510]
0012f2b8 0044ba21 0012f2e0 0012f3f8 0012f3f8
testkjs!KJS::IfNode::execute+0x64
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
2192]
0012f308 004461c9 0012f340 0012f3f8 0052f648
testkjs!KJS::SourceElementsNode::execute+0x211
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
3261]
0012f320 0041f83e 0012f340 0012f3f8 0012f3f8
testkjs!KJS::BlockNode::execute+0x69
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
2119]
0012f358 0041edc3 0012f3bc 0012f3f8 0012f378
testkjs!KJS::DeclaredFunctionImp::execute+0x3e
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\function.cpp @
348]
0012f41c 004559d2 0012f684 00345ff0 0012f498
testkjs!KJS::FunctionImp::callAsFunction+0x1f3
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\function.cpp @
112]
0012f440 0044106a 0012f684 00345ff0 0012f498
testkjs!KJS::ObjectImp::call+0xa2
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\object.cpp @ 95]
0012f4d0 0044475a 0012f684 004ff5a4 0012f4ec
testkjs!KJS::FunctionCallResolveNode::evaluate+0x2aa
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @ 824]
0012f524 00446354 0012f684 0012f574 0012f540
testkjs!KJS::AssignDotNode::evaluate+0xaa
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
1722]
0012f544 0044ba21 0012f56c 0012f684 00518d68
testkjs!KJS::ExprStatementNode::execute+0x64
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
2157]
0012f594 004461c9 0012f5cc 0012f684 0051ade8
testkjs!KJS::SourceElementsNode::execute+0x211
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
3261]
0012f5ac 0041f83e 0012f5cc 0012f684 0012f684
testkjs!KJS::BlockNode::execute+0x69
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @
2119]
0012f5e4 0041edc3 0012f648 0012f684 0c000000
testkjs!KJS::DeclaredFunctionImp::execute+0x3e
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\function.cpp @
348]
0012f6a8 004559d2 0012f9ac 0034c1b8 0012f768
testkjs!KJS::FunctionImp::callAsFunction+0x1f3
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\function.cpp @
112]
0012f6cc 0041f7bc 0012f9ac 0034c1b8 0012f768
testkjs!KJS::ObjectImp::call+0xa2
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\object.cpp @ 95]
0012f714 004409cb 0012f9ac 0012f768 0012f784
testkjs!KJS::DeclaredFunctionImp::construct+0xcc
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\function.cpp @
338]
0012f784 00444b9f 0012f9ac 0034c0e0 0012f7b0
testkjs!KJS::NewExprNode::evaluate+0x22b
[c:\kjk\src\ext\libs\webcore-apple\javascriptcore\kjs\nodes.cpp @ 723]

i.e. it's executing
  ValueImp *v1 = expr1->evaluate(exec);
 in:
ValueImp *EqualNode::evaluate(ExecState *exec)
{
  ValueImp *v1 = expr1->evaluate(exec);
  KJS_CHECKEXCEPTIONVALUE
  ValueImp *v2 = expr2->evaluate(exec);
  KJS_CHECKEXCEPTIONVALUE

- follow execution down to Collector::allocate(size_t s) where newCell
is being allocated. In my case:
0:000> dd newCell l1
0012f140  0034c2d0

i.e. the address of newCell variable is 0x34c2d0
- step through the code. You'll see that function
AllocatedValueImp *jsString(const char *s)
{
    return new StringImp(s ? s : "");
}
returns CollectorCell addr+4 and that's what ValueImp* v1 has:
0:000> dd v1 l1
0012f238  0034c2d4
0:000> dv
           this = 0x0052e300
           exec = 0x0012f3f8
         result = false
             v2 = 0x00000001
             v1 = 0x0034c2d4  <== not 0x34c2d4

I.e. a cast of AllocatedValueImp to ValueImp is done by adding 4 to
the pointer (which simply skips over virtual table pointer, since
that's the first value in the object with virtual functions):
0:000> dds 0034c2d0
0034c2d0  004f8368 testkjs!KJS::StringImp::`vftable'
0034c2d4  00000000
0034c2d8  0059f3f0

This matches the behaviour of ValueImp::downcast() which substracts 4 bytes.

-- kjk



More information about the webkit-dev mailing list