Call AdjustAmountOfExternalAllocatedMemory when V8ArrayBuffer constructed and destructed
https://bugs.webkit.org/show_bug.cgi?id=92993

Patch by Ulan Degenbaev <ulan@chromium.org> on 2012-08-21
Reviewed by Kenneth Russell.

Call AdjustAmountOfExternalAllocatedMemory when V8ArrayBuffer
is constructed and destructed so that V8's garbage collection
heuristics can account for the memory held by these objects.

.:

* ManualTests/typed-array-memory.html: Added.

Source/WebCore:

* WebCore.gypi:
* bindings/v8/SerializedScriptValue.cpp:
* bindings/v8/custom/V8ArrayBufferCustom.cpp:
(WebCore::V8ArrayBufferDeallocationObserver::instance):
(WebCore):
(WebCore::V8ArrayBuffer::constructorCallback):
* bindings/v8/custom/V8ArrayBufferCustom.h: Added.
(WebCore):
* bindings/v8/custom/V8ArrayBufferViewCustom.cpp:
* bindings/v8/custom/V8ArrayBufferViewCustom.h:
(WebCore::constructWebGLArray):
* dom/MessageEvent.cpp:
(WebCore::MessageEvent::MessageEvent):
(WebCore::MessageEvent::initMessageEvent):

Source/WTF:

* wtf/ArrayBuffer.h:
(WTF):
(ArrayBufferDeallocationObserver):
(WTF::ArrayBufferContents::ArrayBufferContents):
(WTF::ArrayBufferContents::transfer):
(ArrayBufferContents):
(ArrayBuffer):
(WTF::ArrayBuffer::setDeallocationObserver):
(WTF::ArrayBufferContents::~ArrayBufferContents):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@126196 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/ManualTests/typed-array-memory.html b/ManualTests/typed-array-memory.html
new file mode 100644
index 0000000..1d62429
--- /dev/null
+++ b/ManualTests/typed-array-memory.html
@@ -0,0 +1,221 @@
+<html>
+<head>
+<title>ArrayBuffer External Memory test</title>
+<script>
+
+var log;
+function print(message, color)
+{
+    var paragraph = document.createElement("div");
+    paragraph.appendChild(document.createTextNode(message));
+    paragraph.style.fontFamily = "monospace";
+    if (color)
+        paragraph.style.color = color;
+    log.appendChild(paragraph);
+}
+
+function pass(msg)
+{
+    print("PASS: " + msg, "green");
+}
+
+function fail(msg)
+{
+    print("FAIL: " + msg, "red");
+}
+
+var KB = 1024;
+var MB = KB * KB;
+var noise = KB;
+
+function externalMemory() {
+    return getV8Statistics().amount_of_external_allocated_memory;
+}
+
+function collectGarbage() {
+    for (var i = 0; i < 10; i++) gc();
+}
+
+function allocationsThatIncreaseExternalMemory() {
+    function test(expression) {
+        var before = externalMemory();
+        (function () { eval(expression); }) ();
+        var now = externalMemory();
+        if (now < before + MB - noise) {
+            fail(expression + " did not increase the amount of external memory (" +
+                  before + ", " + now + ").");
+        } else {
+            pass(expression + " increased the amount of external memory.");
+        }
+        collectGarbage();
+        var after = externalMemory();
+        if (after > now + noise) {
+            fail("Garbage collection after " + expression +
+                 " did not return the amount of external memory to the initial value (" +
+                 now + ", " + after + ").");
+        } else {
+            pass("Garbage collection after " + expression +
+                 " returned the amount of external memory to the initial value.");
+        }
+    }
+
+    test("(new ArrayBuffer(MB))");
+    test("(new Float32Array(MB))");
+    test("(new Float64Array(MB))");
+    test("(new Int8Array(MB))");
+    test("(new Int16Array(MB))");
+    test("(new Int32Array(MB))");
+    test("(new Uint8Array(MB))");
+    test("(new Uint16Array(MB))");
+    test("(new Uint32Array(MB))");
+    var largeJSArray = [];
+    for (var i = 0; i < MB; i++) largeJSArray.push(i);
+    test("(new Float32Array(largeJSArray))");
+    test("(new Float64Array(largeJSArray))");
+    test("(new Int8Array(largeJSArray))");
+    test("(new Int16Array(largeJSArray))");
+    test("(new Int32Array(largeJSArray))");
+    test("(new Uint8Array(largeJSArray))");
+    test("(new Uint16Array(largeJSArray))");
+    test("(new Uint32Array(largeJSArray))");
+    var int8Array = new Int8Array(MB);
+    test("(new  Float32Array(int8Array))");
+    test("(new  Float64Array(int8Array))");
+    test("(new  Int8Array(int8Array))");
+    test("(new  Int16Array(int8Array))");
+    test("(new  Int32Array(int8Array))");
+    test("(new  Uint8Array(int8Array))");
+    test("(new  Uint16Array(int8Array))");
+    test("(new  Uint32Array(int8Array))");
+}
+
+
+function allocationsThatDoNotChangeExternalMemory() {
+    function test(expression) {
+        var before = externalMemory();
+        (function () { eval(expression); }) ();
+        var now = externalMemory();
+        if (now > before + noise) {
+            fail(expression + " increased the amount of external memory (" + before + ", " + now + ").");
+        } else {
+            pass(expression + " did not increase the amount of external memory.");
+        }
+        collectGarbage();
+        var after = externalMemory();
+        if (after < now - noise) {
+            fail("Garbage collection after " + expression + " decreased the amount of external memory (" +
+                 now + ", " + after + ").");
+        } else {
+            pass("Garbage collection after " + expression +
+                 " did not decrease the amount of external memory.");
+        }
+    }
+    var arrayBuffer = new ArrayBuffer(MB);
+    test("(new  Float32Array(arrayBuffer))");
+    test("(new  Float64Array(arrayBuffer))");
+    test("(new  Int8Array(arrayBuffer))");
+    test("(new  Int16Array(arrayBuffer))");
+    test("(new  Int32Array(arrayBuffer))");
+    test("(new  Uint8Array(arrayBuffer))");
+    test("(new  Uint16Array(arrayBuffer))");
+    test("(new  Uint32Array(arrayBuffer))");
+    var int8Array = new Int8Array(MB);
+    test("(new  Float32Array(int8Array.buffer))");
+    test("(new  Float64Array(int8Array.buffer))");
+    test("(new  Int8Array(int8Array.buffer))");
+    test("(new  Int16Array(int8Array.buffer))");
+    test("(new  Int32Array(int8Array.buffer))");
+    test("(new  Uint8Array(int8Array.buffer))");
+    test("(new  Uint16Array(int8Array.buffer))");
+    test("(new  Uint32Array(int8Array.buffer))");
+}
+
+
+function transfersThatDecreaseExternalMemory() {
+    var workerSource =
+"function externalMemory() {\n" +
+"    return getV8Statistics().amount_of_external_allocated_memory;\n" +
+"}\n" +
+"function collectGarbage() {\n" +
+"    for (var i = 0; i < 10; i++) gc();\n" +
+"}\n" +
+"var before = externalMemory();\n" +
+"self.onmessage = function(e) {\n" +
+"    var now = externalMemory();\n" +
+"    e.data = null;\n" +
+"    collectGarbage();\n" +
+"    var after = externalMemory();\n" +
+"    self.postMessage(before + ' ' + now + ' ' + after);\n" +
+"}\n";
+
+    var blob = new Blob([workerSource]);
+    var worker = new Worker(window.webkitURL.createObjectURL(blob));
+    worker.onmessage = function (e) {
+        print("message from worker: " + e.data, "blue");
+    }
+    function test(expression)
+    {
+        var buffer = eval(expression);
+        try {
+            var before = externalMemory();
+            worker.webkitPostMessage(buffer, [buffer]);
+            var now = externalMemory();
+            if (now > before - MB + noise) {
+                fail("Transfer of " + expression + " did not decrease the amount of external memory (" +
+                     before + ", " + now + ").");
+            } else {
+                pass("Transfer of " + expression + " decreased the amount of external memory.");
+            }
+            collectGarbage();
+            var after = externalMemory();
+            if (after < now - noise) {
+                fail("Garbage collection after transfer of " + expression +
+                     " decreased the amount of external memory (" + now + ", " + after + ").");
+            } else {
+                pass("Garbage collection after transfer of " + expression +
+                     " did not decrease the amount of external memory.");
+            }
+        } catch (e) {
+            fail("Transfer of " + name + ": could not webkitPostMessage: " + e);
+            return false;
+        }
+        return true;
+    }
+    test("(new ArrayBuffer(MB))");
+    test("(new Float32Array(MB)).buffer");
+    test("(new Float64Array(MB)).buffer");
+    test("(new Int8Array(MB)).buffer");
+    test("(new Int16Array(MB)).buffer");
+    test("(new Int32Array(MB)).buffer");
+    test("(new Uint8Array(MB)).buffer");
+    test("(new Uint16Array(MB)).buffer");
+    test("(new Uint32Array(MB)).buffer");
+}
+
+
+function runAll() {
+    log = document.getElementById("log1");
+    if (typeof gc == "undefined" || typeof getV8Statistics == "undefined") {
+        print("Run chrome browser with --js-flags='--expose_gc --track_gc_object_stats'", "red");
+    } else {
+         allocationsThatIncreaseExternalMemory();
+         collectGarbage();
+         allocationsThatDoNotChangeExternalMemory();
+         collectGarbage();
+         log = document.getElementById("log2");
+         transfersThatDecreaseExternalMemory();
+         collectGarbage();
+    }
+}
+
+</script>
+</head>
+<body onload="runAll()">
+<p>This test checks that allocation and deallocation of typed arrays correctly
+adjusts the amount of external memory in V8.</p>
+<div id='log1'></div>
+<p>This test checks that transfer of an array buffer to worker decreases amount of
+external memory in the main V8 isolate.</p>
+<div id='log2'></div>
+</body>
+</html>