WebAssembly: perform stack checks
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 18 May 2017 19:38:10 +0000 (19:38 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 18 May 2017 19:38:10 +0000 (19:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=165546
<rdar://problem/29760307>

Reviewed by Filip Pizlo.

JSTests:

* wasm.yaml:
* wasm/function-tests/factorial.js:
* wasm/function-tests/float-sub.js:
* wasm/function-tests/stack-overflow.js: Added.
(import.Builder.from.string_appeared_here.import.as.assert.from.string_appeared_here.makeInstance):
(import.Builder.from.string_appeared_here.import.as.assert.from.string_appeared_here.assertOverflows):
(assertOverflows.makeInstance):
(assertOverflows.makeInstance2):
(assertOverflows.assertThrows):
(assertOverflows):
(assertThrows.test.makeSignature):
(assertThrows.test.makeInstance):
(assertThrows.test):
(assertThrows):

Source/JavaScriptCore:

This patch adds stack checks to wasm. It implements it by storing the stack
bounds on the Context.

Stack checking works as normal, except we do a small optimization for terminal
nodes in the call tree (nodes that don't make any calls). These nodes will
only do a stack check if their frame size is beyond 1024 bytes. Otherwise,
it's assumed the parent that called them did their stack check for them.
This is because all things that make calls make sure to do an extra 1024
bytes whenever doing a stack check.

We also take into account stack size for potential JS calls when doing
stack checks since our JS stubs don't do this on their own. Each frame
will ensure it does a stack check large enough for any potential JS call
stubs it'll execute.

Surprisingly, this patch is neutral on WasmBench and TitzerBench.

* llint/LLIntData.cpp:
(JSC::LLInt::Data::performAssertions):
* llint/LowLevelInterpreter.asm:
* runtime/Error.cpp:
(JSC::createRangeError):
(JSC::addErrorInfoAndGetBytecodeOffset):
I fixed a bug here where we assumed that the first frame that has line
and column info would be in our stack trace. This is not correct
since we limit our stack trace size. If everything in our limited
size stack trace is Wasm, then we won't have any frames with line
and column info.
* runtime/Error.h:
* runtime/ExceptionHelpers.cpp:
(JSC::createStackOverflowError):
* runtime/ExceptionHelpers.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::webAssemblyToJSCalleeStructure):
* runtime/JSType.h:
* runtime/Options.h: I've added a new option that controls
whether or not we use fast TLS for the wasm context.
* runtime/VM.cpp:
(JSC::VM::VM):
* runtime/VM.h:
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::B3IRGenerator):
* wasm/WasmBinding.cpp:
(JSC::Wasm::wasmToWasm):
* wasm/WasmContext.cpp:
(JSC::Wasm::loadContext):
(JSC::Wasm::storeContext):
* wasm/WasmContext.h:
(JSC::Wasm::useFastTLSForContext):
* wasm/WasmExceptionType.h:
* wasm/WasmMemoryInformation.h:
(JSC::Wasm::PinnedRegisterInfo::toSave):
* wasm/WasmThunks.cpp:
(JSC::Wasm::throwExceptionFromWasmThunkGenerator):
(JSC::Wasm::throwStackOverflowFromWasmThunkGenerator):
(JSC::Wasm::Thunks::stub):
* wasm/WasmThunks.h:
* wasm/js/JSWebAssemblyInstance.h:
(JSC::JSWebAssemblyInstance::offsetOfCachedStackLimit):
(JSC::JSWebAssemblyInstance::cachedStackLimit):
(JSC::JSWebAssemblyInstance::setCachedStackLimit):
* wasm/js/JSWebAssemblyModule.cpp:
(JSC::JSWebAssemblyModule::finishCreation):
* wasm/js/WebAssemblyFunction.cpp:
(JSC::callWebAssemblyFunction):
* wasm/js/WebAssemblyToJSCallee.cpp: Make this a descendent of object.
This is needed for correctness because we may call into JS,
and then the first JS frame could stack overflow. When it stack
overflows, it rolls back one frame to the wasm->js call stub with
the wasm->js callee. It gets the lexical global object from this
frame, meaning it gets the global object from the callee. Therefore,
we must make it an object since all objects have global objects.
(JSC::WebAssemblyToJSCallee::create):
* wasm/js/WebAssemblyToJSCallee.h:

Tools:

Add some new testing modes for using and not using fast TLS wasm contexts.

* Scripts/run-jsc-stress-tests:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@217060 268f45cc-cd09-0410-ab3c-d52691b4dbfc

33 files changed:
JSTests/ChangeLog
JSTests/wasm.yaml
JSTests/wasm/function-tests/factorial.js
JSTests/wasm/function-tests/float-sub.js
JSTests/wasm/function-tests/stack-overflow.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/llint/LLIntData.cpp
Source/JavaScriptCore/llint/LowLevelInterpreter.asm
Source/JavaScriptCore/runtime/Error.cpp
Source/JavaScriptCore/runtime/Error.h
Source/JavaScriptCore/runtime/ExceptionHelpers.cpp
Source/JavaScriptCore/runtime/ExceptionHelpers.h
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/JavaScriptCore/runtime/JSType.h
Source/JavaScriptCore/runtime/Options.h
Source/JavaScriptCore/runtime/VM.cpp
Source/JavaScriptCore/runtime/VM.h
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmBinding.cpp
Source/JavaScriptCore/wasm/WasmContext.cpp
Source/JavaScriptCore/wasm/WasmContext.h
Source/JavaScriptCore/wasm/WasmExceptionType.h
Source/JavaScriptCore/wasm/WasmMemoryInformation.h
Source/JavaScriptCore/wasm/WasmThunks.cpp
Source/JavaScriptCore/wasm/WasmThunks.h
Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h
Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyFunction.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyToJSCallee.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyToJSCallee.h
Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests

index cefbcb6..abd02a8 100644 (file)
@@ -1,3 +1,26 @@
+2017-05-18  Saam Barati  <sbarati@apple.com>
+
+        WebAssembly: perform stack checks
+        https://bugs.webkit.org/show_bug.cgi?id=165546
+        <rdar://problem/29760307>
+
+        Reviewed by Filip Pizlo.
+
+        * wasm.yaml:
+        * wasm/function-tests/factorial.js:
+        * wasm/function-tests/float-sub.js:
+        * wasm/function-tests/stack-overflow.js: Added.
+        (import.Builder.from.string_appeared_here.import.as.assert.from.string_appeared_here.makeInstance):
+        (import.Builder.from.string_appeared_here.import.as.assert.from.string_appeared_here.assertOverflows):
+        (assertOverflows.makeInstance):
+        (assertOverflows.makeInstance2):
+        (assertOverflows.assertThrows):
+        (assertOverflows):
+        (assertThrows.test.makeSignature):
+        (assertThrows.test.makeInstance):
+        (assertThrows.test):
+        (assertThrows):
+
 2017-05-18  Keith Miller  <keith_miller@apple.com>
 
         WebAssembly API: test with neutered inputs
index 6488f6d..28e747e 100644 (file)
   cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/call.wast.js
-  cmd: runWebAssemblySpecTest :skip
+  cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/call_indirect.wast.js
-  cmd: runWebAssemblySpecTest :skip
+  cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/comments.wast.js
   cmd: runWebAssemblySpecTest :normal
@@ -86,7 +86,7 @@
   cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/fac.wast.js
-  cmd: runWebAssemblySpecTest :skip
+  cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/float_literals.wast.js
   cmd: runWebAssemblySpecTest :normal
   cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/skip-stack-guard-page.wast.js
-  cmd: runWebAssemblySpecTest :skip
+  cmd: runWebAssemblySpecTest :normal
 
 - path: wasm/spec-tests/stack.wast.js
   cmd: runWebAssemblySpecTest :normal
index c5e2ce6..d01d373 100644 (file)
@@ -1,30 +1,36 @@
+import * as assert from '../assert.js'
 import Builder from '../Builder.js'
 
 const b = new Builder();
 b.Type().End()
+    .Import().End()
     .Function().End()
+    .Export()
+        .Function("fac")
+    .End()
     .Code()
-    .Function({ params: ["i32"], ret: "i32" }, [])
-    .GetLocal(0)
-    .I32Const(0)
-    .I32Eq()
-    .If("void", b =>
-        b.I32Const(1)
-        .Return()
-       )
-        .GetLocal(0)
-    .GetLocal(0)
-    .I32Const(1)
-    .I32Sub()
-    .Call(0)
-    .I32Mul()
-    .Return()
+        .Function("fac", { params: ["i32"], ret: "i32" })
+            .GetLocal(0)
+            .I32Const(0)
+            .I32Eq()
+            .If("void", b =>
+                b.I32Const(1)
+                .Return()
+               )
+                .GetLocal(0)
+            .GetLocal(0)
+            .I32Const(1)
+            .I32Sub()
+            .Call(0)
+            .I32Mul()
+            .Return()
+        .End()
     .End()
 
-const bin = b.WebAssembly()
-bin.trim();
-testWasmModuleFunctions(bin.get(), 1, [[{type: "i32", value: 1 }, [{ type: "i32", value: 0 }]],
-                                       [{type: "i32", value: 1 }, [{ type: "i32", value: 1 }]],
-                                       [{type: "i32", value: 2 }, [{ type: "i32", value: 2 }]],
-                                       [{type: "i32", value: 24 }, [{ type: "i32", value: 4 }]],
-                                      ]);
+const m = new WebAssembly.Module(b.WebAssembly().get());
+const fac = (new WebAssembly.Instance(m)).exports.fac;
+assert.eq(fac(0), 1);
+assert.eq(fac(1), 1);
+assert.eq(fac(2), 2);
+assert.eq(fac(4), 24);
+assert.throws(() => fac(1e7), RangeError, "Maximum call stack size exceeded.");
index 01c9ea1..e29ab7a 100644 (file)
@@ -1,30 +1,42 @@
+import * as assert from '../assert.js'
 import Builder from '../Builder.js'
 
 const b = new Builder();
 b.Type().End()
     .Function().End()
+    .Export()
+        .Function("foo")
+        .Function("bar")
+    .End()
     .Code()
-    .Function({ params: ["f32", "f32"], ret: "f32" }, [])
+    .Function("bar", { params: ["f32", "f32"], ret: "f32" }, [])
     .GetLocal(0)
     .GetLocal(1)
     .F32Sub()
     .Return()
     .End()
 
-    .Function({ params: ["f32", "f32"], ret: "f32" }, [])
+    .Function("foo", { params: ["f32", "f32"], ret: "f32" }, [])
     .GetLocal(0)
     .GetLocal(1)
     .Call(0)
     .Return()
     .End()
+    .End()
 
 const bin = b.WebAssembly()
 bin.trim();
-testWasmModuleFunctions(bin.get(), 2,
-                        [[{type: "f32", value: -1.5 }, [{ type: "f32", value: 0 }, { type: "f32", value: 1.5 }]],
-                         [{type: "f32", value: 87.6234 }, [{ type: "f32", value: 100.1234 }, { type: "f32", value: 12.5 }]]
-                        ],
-                        [[{type: "f32", value: -1.5 }, [{ type: "f32", value: 0 }, { type: "f32", value: 1.5 }]],
-                         [{type: "f32", value: 87.6234 }, [{ type: "f32", value: 100.1234 }, { type: "f32", value: 12.5 }]]
-                        ]
-                       );
+const instance = new WebAssembly.Instance(new WebAssembly.Module(bin.get()));
+
+let x = new Float32Array(3);
+x[0] = 0;
+x[1] = 1.5;
+x[2] = x[0] - x[1];
+assert.eq(instance.exports.bar(x[0], x[1]), x[2]);
+assert.eq(instance.exports.foo(x[0], x[1]), x[2]);
+
+x[0] = 100.1234
+x[1] = 12.5;
+x[2] = x[0] - x[1];
+assert.eq(instance.exports.bar(x[0], x[1]), x[2]);
+assert.eq(instance.exports.foo(x[0], x[1]), x[2]);
diff --git a/JSTests/wasm/function-tests/stack-overflow.js b/JSTests/wasm/function-tests/stack-overflow.js
new file mode 100644 (file)
index 0000000..3b439bd
--- /dev/null
@@ -0,0 +1,221 @@
+import Builder from '../Builder.js'
+import * as assert from '../assert.js'
+
+{
+    function makeInstance() {
+        const tableDescription = {initial: 1, element: "anyfunc"};
+        const builder = new Builder()
+            .Type()
+                .Func(["i32"], "void")
+            .End()
+            .Import()
+                .Table("imp", "table", tableDescription)
+            .End()
+            .Function().End()
+            .Export()
+                .Function("foo")
+            .End()
+            .Code()
+                .Function("foo", 0 /*['i32'] => 'void'*/)
+                    .GetLocal(0) // parameter to call
+                    .GetLocal(0) // call index
+                    .CallIndirect(0, 0) // calling function of type ['i32'] => 'i32'
+                    .Return()
+                .End()
+            .End();
+
+
+        const bin = builder.WebAssembly().get();
+        const module = new WebAssembly.Module(bin);
+        const table = new WebAssembly.Table(tableDescription);
+        return {instance: new WebAssembly.Instance(module, {imp: {table}}), table};
+    }
+
+    const {instance: i1, table: t1} = makeInstance();
+    const {instance: i2, table: t2} = makeInstance();
+    t2.set(0, i1.exports.foo);
+    t1.set(0, i2.exports.foo);
+
+    function assertOverflows(instance) {
+        let stack;
+        try {
+            instance.exports.foo(0)
+        } catch(e) {
+            stack = e.stack;
+        }
+        stack = stack.split("\n");
+        assert.truthy(stack.length > 50);
+        for (let i = 0; i < 50; ++i) {
+            let item = stack[stack.length - i - 1];
+            assert.eq(item, "wasm function: 0@[wasm code]");
+        } 
+    }
+    assertOverflows(i1);
+    assertOverflows(i2);
+
+}
+
+{
+    function makeInstance() {
+        const tableDescription = {initial: 1, element: "anyfunc"};
+        const builder = new Builder()
+            .Type()
+                .Func([], "void")
+            .End()
+            .Import()
+                .Table("imp", "table", tableDescription)
+            .End()
+            .Function().End()
+            .Export()
+                .Function("foo")
+            .End()
+            .Code()
+                .Function("foo", {params:["i32"], ret:"void"})
+                    .GetLocal(0) // parameter to call
+                    .GetLocal(0) // call index
+                    .CallIndirect(0, 0) // calling function of type [] => 'void'
+                    .Return()
+                .End()
+            .End();
+
+
+        const bin = builder.WebAssembly().get();
+        const module = new WebAssembly.Module(bin);
+        const table = new WebAssembly.Table(tableDescription);
+        return {instance: new WebAssembly.Instance(module, {imp: {table}}), table};
+    }
+
+    function makeInstance2(f) {
+        const builder = new Builder()
+            .Type()
+            .End()
+            .Import()
+                .Function("imp", "f", {params:['i32'], ret:"void"})
+            .End()
+            .Function().End()
+            .Export()
+                .Function("foo")
+            .End()
+            .Code()
+                .Function("foo", {params: [], ret: "void" })
+                    .I32Const(0)
+                    .Call(0)
+                    .Return()
+                .End()
+            .End();
+
+
+        const bin = builder.WebAssembly().get();
+        const module = new WebAssembly.Module(bin);
+        return new WebAssembly.Instance(module, {imp: {f}});
+    }
+
+    const {instance: i1, table: t1} = makeInstance();
+    const foo = i1.exports.foo;
+    const i2 = makeInstance2(i1.exports.foo);
+    t1.set(0, i2.exports.foo);
+
+    function assertThrows(instance) {
+        let stack;
+        try {
+            instance.exports.foo();
+        } catch(e) {
+            stack = e.stack;
+        }
+        assert.truthy(stack);
+
+        stack = stack.split("\n");
+        assert.truthy(stack.length > 50);
+        const oneString = "wasm function: 1@[wasm code]";
+        const zeroString = "wasm function: 0@[wasm code]";
+        let currentIndex = stack[stack.length - 1] === oneString ? 1 : 0;
+        for (let i = 0; i < 50; ++i) {
+            let item = stack[stack.length - 1 - i];
+            if (currentIndex === 1) {
+                assert.eq(item, oneString);
+                currentIndex = 0;
+            } else {
+                assert.eq(currentIndex, 0);
+                assert.eq(item, zeroString);
+                currentIndex = 1;
+            }
+        }
+    }
+
+    for (let i = 0; i < 20; ++i) {
+        assertThrows(i2);
+        assertThrows(i1);
+    }
+
+    for (let i = 0; i < 20; ++i) {
+        assertThrows(i1);
+        assertThrows(i2);
+    }
+}
+
+{
+    function test(numArgs) {
+        function makeSignature() {
+            let args = [];
+            for (let i = 0; i < numArgs; ++i) {
+                args.push("i32");
+            }
+            return {params: args};
+        }
+        function makeInstance(f) {
+            let builder = new Builder()
+                .Type()
+                    .Func([], "void")
+                .End()
+                .Import()
+                    .Function("imp", "f", makeSignature())
+                .End()
+                .Function().End()
+                .Export()
+                    .Function("foo")
+                .End()
+                .Code()
+                    .Function("foo", {params:[], ret:"void"});
+            for (let i = 0; i < numArgs; ++i) {
+                builder = builder.I32Const(i);
+            }
+
+            builder = builder.Call(0).Return().End().End();
+            const bin = builder.WebAssembly().get();
+            const module = new WebAssembly.Module(bin);
+            return new WebAssembly.Instance(module, {imp: {f}});
+        }
+
+        function f(...args) {
+            assert.eq(args.length, numArgs);
+            for (let i = 0; i < args.length; ++i)
+                assert.eq(args[i], i);
+
+            instance.exports.foo();
+        }
+        let instance = makeInstance(f);
+
+        let stack;
+        try {
+            instance.exports.foo();
+        } catch(e) {
+            stack = e.stack;
+        }
+        assert.truthy(stack.split("\n").length > 25);
+    }
+
+    for (let i = 0; i < 70; ++i) {
+        let r = Math.random() * 1000 | 0;
+        test(r);
+    }
+
+    test(20);
+    test(20);
+    test(1000);
+    test(2);
+    test(1);
+    test(0);
+    test(700);
+    test(433);
+    test(42);
+}
index d8a3ff8..8704a05 100644 (file)
@@ -1,3 +1,89 @@
+2017-05-18  Saam Barati  <sbarati@apple.com>
+
+        WebAssembly: perform stack checks
+        https://bugs.webkit.org/show_bug.cgi?id=165546
+        <rdar://problem/29760307>
+
+        Reviewed by Filip Pizlo.
+
+        This patch adds stack checks to wasm. It implements it by storing the stack
+        bounds on the Context.
+        
+        Stack checking works as normal, except we do a small optimization for terminal
+        nodes in the call tree (nodes that don't make any calls). These nodes will
+        only do a stack check if their frame size is beyond 1024 bytes. Otherwise,
+        it's assumed the parent that called them did their stack check for them.
+        This is because all things that make calls make sure to do an extra 1024
+        bytes whenever doing a stack check.
+        
+        We also take into account stack size for potential JS calls when doing
+        stack checks since our JS stubs don't do this on their own. Each frame
+        will ensure it does a stack check large enough for any potential JS call
+        stubs it'll execute.
+        
+        Surprisingly, this patch is neutral on WasmBench and TitzerBench.
+
+        * llint/LLIntData.cpp:
+        (JSC::LLInt::Data::performAssertions):
+        * llint/LowLevelInterpreter.asm:
+        * runtime/Error.cpp:
+        (JSC::createRangeError):
+        (JSC::addErrorInfoAndGetBytecodeOffset):
+        I fixed a bug here where we assumed that the first frame that has line
+        and column info would be in our stack trace. This is not correct
+        since we limit our stack trace size. If everything in our limited
+        size stack trace is Wasm, then we won't have any frames with line
+        and column info.
+        * runtime/Error.h:
+        * runtime/ExceptionHelpers.cpp:
+        (JSC::createStackOverflowError):
+        * runtime/ExceptionHelpers.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        (JSC::JSGlobalObject::visitChildren):
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::webAssemblyToJSCalleeStructure):
+        * runtime/JSType.h:
+        * runtime/Options.h: I've added a new option that controls
+        whether or not we use fast TLS for the wasm context.
+        * runtime/VM.cpp:
+        (JSC::VM::VM):
+        * runtime/VM.h:
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::B3IRGenerator):
+        * wasm/WasmBinding.cpp:
+        (JSC::Wasm::wasmToWasm):
+        * wasm/WasmContext.cpp:
+        (JSC::Wasm::loadContext):
+        (JSC::Wasm::storeContext):
+        * wasm/WasmContext.h:
+        (JSC::Wasm::useFastTLSForContext):
+        * wasm/WasmExceptionType.h:
+        * wasm/WasmMemoryInformation.h:
+        (JSC::Wasm::PinnedRegisterInfo::toSave):
+        * wasm/WasmThunks.cpp:
+        (JSC::Wasm::throwExceptionFromWasmThunkGenerator):
+        (JSC::Wasm::throwStackOverflowFromWasmThunkGenerator):
+        (JSC::Wasm::Thunks::stub):
+        * wasm/WasmThunks.h:
+        * wasm/js/JSWebAssemblyInstance.h:
+        (JSC::JSWebAssemblyInstance::offsetOfCachedStackLimit):
+        (JSC::JSWebAssemblyInstance::cachedStackLimit):
+        (JSC::JSWebAssemblyInstance::setCachedStackLimit):
+        * wasm/js/JSWebAssemblyModule.cpp:
+        (JSC::JSWebAssemblyModule::finishCreation):
+        * wasm/js/WebAssemblyFunction.cpp:
+        (JSC::callWebAssemblyFunction):
+        * wasm/js/WebAssemblyToJSCallee.cpp: Make this a descendent of object.
+        This is needed for correctness because we may call into JS,
+        and then the first JS frame could stack overflow. When it stack
+        overflows, it rolls back one frame to the wasm->js call stub with
+        the wasm->js callee. It gets the lexical global object from this
+        frame, meaning it gets the global object from the callee. Therefore,
+        we must make it an object since all objects have global objects.
+        (JSC::WebAssemblyToJSCallee::create):
+        * wasm/js/WebAssemblyToJSCallee.h:
+
 2017-05-18  Keith Miller  <keith_miller@apple.com>
 
         WebAssembly API: test with neutered inputs
index 0aff108..8b615a1 100644 (file)
@@ -156,21 +156,21 @@ void Data::performAssertions(VM& vm)
     
     STATIC_ASSERT(StringType == 6);
     STATIC_ASSERT(SymbolType == 7);
-    STATIC_ASSERT(ObjectType == 24);
-    STATIC_ASSERT(FinalObjectType == 25);
-    STATIC_ASSERT(JSFunctionType == 27);
-    STATIC_ASSERT(ArrayType == 35);
-    STATIC_ASSERT(DerivedArrayType == 36);
-    STATIC_ASSERT(ProxyObjectType == 54);
-    STATIC_ASSERT(Int8ArrayType == 37);
-    STATIC_ASSERT(Int16ArrayType == 38);
-    STATIC_ASSERT(Int32ArrayType == 39);
-    STATIC_ASSERT(Uint8ArrayType == 40);
-    STATIC_ASSERT(Uint8ClampedArrayType == 41);
-    STATIC_ASSERT(Uint16ArrayType == 42);
-    STATIC_ASSERT(Uint32ArrayType == 43);
-    STATIC_ASSERT(Float32ArrayType == 44);
-    STATIC_ASSERT(Float64ArrayType == 45);
+    STATIC_ASSERT(ObjectType == 23);
+    STATIC_ASSERT(FinalObjectType == 24);
+    STATIC_ASSERT(JSFunctionType == 26);
+    STATIC_ASSERT(ArrayType == 34);
+    STATIC_ASSERT(DerivedArrayType == 35);
+    STATIC_ASSERT(ProxyObjectType == 53);
+    STATIC_ASSERT(Int8ArrayType == 36);
+    STATIC_ASSERT(Int16ArrayType == 37);
+    STATIC_ASSERT(Int32ArrayType == 38);
+    STATIC_ASSERT(Uint8ArrayType == 39);
+    STATIC_ASSERT(Uint8ClampedArrayType == 40);
+    STATIC_ASSERT(Uint16ArrayType == 41);
+    STATIC_ASSERT(Uint32ArrayType == 42);
+    STATIC_ASSERT(Float32ArrayType == 43);
+    STATIC_ASSERT(Float64ArrayType == 44);
     STATIC_ASSERT(MasqueradesAsUndefined == 1);
     STATIC_ASSERT(ImplementsDefaultHasInstance == 2);
     STATIC_ASSERT(FirstConstantRegisterIndex == 0x40000000);
index 3da98be..da9c24e 100644 (file)
@@ -345,24 +345,24 @@ const SlowPutArrayStorageShape = 0x0C
 # Type constants.
 const StringType = 6
 const SymbolType = 7
-const ObjectType = 24
-const FinalObjectType = 25
-const JSFunctionType = 27
-const ArrayType = 35
-const DerivedArrayType = 36
-const ProxyObjectType = 54
+const ObjectType = 23
+const FinalObjectType = 24
+const JSFunctionType = 26
+const ArrayType = 34
+const DerivedArrayType = 35
+const ProxyObjectType = 53
 
 # The typed array types need to be numbered in a particular order because of the manually written
 # switch statement in get_by_val and put_by_val.
-const Int8ArrayType = 37
-const Int16ArrayType = 38
-const Int32ArrayType = 39
-const Uint8ArrayType = 40
-const Uint8ClampedArrayType = 41
-const Uint16ArrayType = 42
-const Uint32ArrayType = 43
-const Float32ArrayType = 44
-const Float64ArrayType = 45
+const Int8ArrayType = 36
+const Int16ArrayType = 37
+const Int32ArrayType = 38
+const Uint8ArrayType = 39
+const Uint8ClampedArrayType = 40
+const Uint16ArrayType = 41
+const Uint32ArrayType = 42
+const Float32ArrayType = 43
+const Float64ArrayType = 44
 
 const FirstArrayType = Int8ArrayType
 const LastArrayType = Float64ArrayType
index 33388c8..9094d52 100644 (file)
@@ -60,8 +60,13 @@ JSObject* createEvalError(ExecState* exec, const String& message, ErrorInstance:
 
 JSObject* createRangeError(ExecState* exec, const String& message, ErrorInstance::SourceAppender appender)
 {
-    ASSERT(!message.isEmpty());
     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+    return createRangeError(exec, globalObject, message, appender);
+}
+
+JSObject* createRangeError(ExecState* exec, JSGlobalObject* globalObject, const String& message, ErrorInstance::SourceAppender appender)
+{
+    ASSERT(!message.isEmpty());
     return ErrorInstance::create(exec, globalObject->vm(), globalObject->rangeErrorConstructor()->errorStructure(), message, appender, TypeNothing, true);
 }
 
@@ -182,7 +187,7 @@ bool addErrorInfoAndGetBytecodeOffset(ExecState* exec, VM& vm, JSObject* obj, bo
             callFrame = functor.foundCallFrame();
             unsigned stackIndex = functor.index();
             *bytecodeOffset = 0;
-            if (stackTrace.at(stackIndex).hasBytecodeOffset())
+            if (stackIndex < stackTrace.size() && stackTrace.at(stackIndex).hasBytecodeOffset())
                 *bytecodeOffset = stackTrace.at(stackIndex).bytecodeOffset();
         }
         
@@ -268,6 +273,11 @@ JSObject* createRangeError(ExecState* exec, const String& message)
     return createRangeError(exec, message, nullptr);
 }
 
+JSObject* createRangeError(ExecState* exec, JSGlobalObject* globalObject, const String& message)
+{
+    return createRangeError(exec, globalObject, message, nullptr);
+}
+
 JSObject* createReferenceError(ExecState* exec, const String& message)
 {
     return createReferenceError(exec, message, nullptr);
index aae660c..330186f 100644 (file)
@@ -52,6 +52,7 @@ enum class ErrorType : uint8_t {
 JSObject* createError(ExecState*, const String&, ErrorInstance::SourceAppender);
 JSObject* createEvalError(ExecState*, const String&, ErrorInstance::SourceAppender);
 JSObject* createRangeError(ExecState*, const String&, ErrorInstance::SourceAppender);
+JSObject* createRangeError(ExecState*, JSGlobalObject*, const String&, ErrorInstance::SourceAppender);
 JSObject* createReferenceError(ExecState*, const String&, ErrorInstance::SourceAppender);
 JSObject* createSyntaxError(ExecState*, const String&, ErrorInstance::SourceAppender);
 JSObject* createTypeError(ExecState*, const String&, ErrorInstance::SourceAppender, RuntimeType);
@@ -62,6 +63,7 @@ JSObject* createURIError(ExecState*, const String&, ErrorInstance::SourceAppende
 JS_EXPORT_PRIVATE JSObject* createError(ExecState*, const String&);
 JS_EXPORT_PRIVATE JSObject* createEvalError(ExecState*, const String&);
 JS_EXPORT_PRIVATE JSObject* createRangeError(ExecState*, const String&);
+JS_EXPORT_PRIVATE JSObject* createRangeError(ExecState*, JSGlobalObject*, const String&);
 JS_EXPORT_PRIVATE JSObject* createReferenceError(ExecState*, const String&);
 JS_EXPORT_PRIVATE JSObject* createSyntaxError(ExecState*, const String&);
 JS_EXPORT_PRIVATE JSObject* createTypeError(ExecState*);
index b5539df..4b826be 100644 (file)
@@ -69,7 +69,12 @@ bool isTerminatedExecutionException(VM& vm, Exception* exception)
 
 JSObject* createStackOverflowError(ExecState* exec)
 {
-    return createRangeError(exec, ASCIILiteral("Maximum call stack size exceeded."));
+    return createStackOverflowError(exec, exec->lexicalGlobalObject());
+}
+
+JSObject* createStackOverflowError(ExecState* exec, JSGlobalObject* globalObject)
+{
+    return createRangeError(exec, globalObject, ASCIILiteral("Maximum call stack size exceeded."));
 }
 
 JSObject* createUndefinedVariableError(ExecState* exec, const Identifier& ident)
index 08e9d18..0549687 100644 (file)
@@ -43,6 +43,7 @@ JSObject* createTerminatedExecutionException(VM*);
 JS_EXPORT_PRIVATE bool isTerminatedExecutionException(VM&, Exception*);
 JS_EXPORT_PRIVATE JSObject* createError(ExecState*, JSValue, const String&, ErrorInstance::SourceAppender);
 JS_EXPORT_PRIVATE JSObject* createStackOverflowError(ExecState*);
+JSObject* createStackOverflowError(ExecState*, JSGlobalObject*);
 JSObject* createUndefinedVariableError(ExecState*, const Identifier&);
 JSObject* createTDZError(ExecState*);
 JSObject* createNotAnObjectError(ExecState*, JSValue);
index 13b5c5b..5eeeef6 100644 (file)
 #include "WeakMapPrototype.h"
 #include "WeakSetConstructor.h"
 #include "WeakSetPrototype.h"
+#include "WebAssemblyToJSCallee.h"
 #include <wtf/RandomNumber.h>
 
 #if ENABLE(INTL)
@@ -887,6 +888,7 @@ putDirectWithoutTransition(vm, vm.propertyNames-> jsName, lowerName ## Construct
         m_webAssemblyModuleRecordStructure.set(vm, this, WebAssemblyModuleRecord::createStructure(vm, this, m_objectPrototype.get()));
         m_webAssemblyFunctionStructure.set(vm, this, WebAssemblyFunction::createStructure(vm, this, m_functionPrototype.get()));
         m_webAssemblyWrapperFunctionStructure.set(vm, this, WebAssemblyWrapperFunction::createStructure(vm, this, m_functionPrototype.get()));
+        m_webAssemblyToJSCalleeStructure.set(vm, this, WebAssemblyToJSCallee::createStructure(vm, this, jsNull()));
         auto* webAssembly = JSWebAssembly::create(vm, this, m_webAssemblyStructure.get());
         putDirectWithoutTransition(vm, Identifier::fromString(exec, "WebAssembly"), webAssembly, DontEnum);
 
@@ -1270,6 +1272,7 @@ void JSGlobalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
     visitor.append(thisObject->m_webAssemblyModuleRecordStructure);
     visitor.append(thisObject->m_webAssemblyFunctionStructure);
     visitor.append(thisObject->m_webAssemblyWrapperFunctionStructure);
+    visitor.append(thisObject->m_webAssemblyToJSCalleeStructure);
     FOR_EACH_WEBASSEMBLY_CONSTRUCTOR_TYPE(VISIT_SIMPLE_TYPE)
 #endif // ENABLE(WEBASSEMBLY)
 
index 305120c..d3935b9 100644 (file)
@@ -351,6 +351,7 @@ public:
     WriteBarrier<Structure> m_webAssemblyModuleRecordStructure;
     WriteBarrier<Structure> m_webAssemblyFunctionStructure;
     WriteBarrier<Structure> m_webAssemblyWrapperFunctionStructure;
+    WriteBarrier<Structure> m_webAssemblyToJSCalleeStructure;
     FOR_EACH_WEBASSEMBLY_CONSTRUCTOR_TYPE(DEFINE_STORAGE_FOR_SIMPLE_TYPE)
 #endif // ENABLE(WEBASSEMBLY)
 
@@ -621,6 +622,7 @@ public:
     Structure* webAssemblyModuleRecordStructure() const { return m_webAssemblyModuleRecordStructure.get(); }
     Structure* webAssemblyFunctionStructure() const { return m_webAssemblyFunctionStructure.get(); }
     Structure* webAssemblyWrapperFunctionStructure() const { return m_webAssemblyWrapperFunctionStructure.get(); }
+    Structure* webAssemblyToJSCalleeStructure() const { return m_webAssemblyToJSCalleeStructure.get(); }
 #endif // ENABLE(WEBASSEMBLY)
 
     JS_EXPORT_PRIVATE void setRemoteDebuggingEnabled(bool);
index ca5aa63..2f77cd0 100644 (file)
@@ -53,8 +53,6 @@ enum JSType : uint8_t {
     JSSourceCodeType,
     JSScriptFetcherType,
 
-    WebAssemblyToJSCalleeType,
-
     // The ObjectType value must come before any JSType that is a subclass of JSObject.
     ObjectType,
     FinalObjectType,
@@ -101,7 +99,9 @@ enum JSType : uint8_t {
 
     ClonedArgumentsType,
 
-    LastJSCObjectType = ClonedArgumentsType,
+    WebAssemblyToJSCalleeType,
+
+    LastJSCObjectType = WebAssemblyToJSCalleeType,
     MaxJSType = 0b11111111,
 };
 
index 5137af8..9c7d053 100644 (file)
@@ -455,6 +455,7 @@ typedef const char* optionString;
     v(bool, crashIfWebAssemblyCantFastMemory, false, Normal, "If true, we will crash if we can't obtain fast memory for wasm.") \
     v(unsigned, webAssemblyFastMemoryPreallocateCount, 0, Normal, "WebAssembly fast memories can be pre-allocated at program startup and remain cached to avoid fragmentation leading to bounds-checked memory. This number is an upper bound on initial allocation as well as total count of fast memories. Zero means no pre-allocation, no caching, and no limit to the number of runtime allocations.") \
     v(bool, useWebAssemblyFastTLS, true, Normal, "If true, we will try to use fast thread-local storage if available on the current platform.") \
+    v(bool, useFastTLSForWasmContext, true, Normal, "If true (and fast TLS is enabled), we will store context in fast TLS. If false, we will pin it to a register.") \
     v(bool, useCallICsForWebAssemblyToJSCalls, true, Normal, "If true, we will use CallLinkInfo to inline cache Wasm to JS calls.")
 
 
index b4105f1..eacf522 100644 (file)
@@ -239,7 +239,6 @@ VM::VM(VMType vmType, HeapType heapType)
     programExecutableStructure.set(*this, ProgramExecutable::createStructure(*this, 0, jsNull()));
     functionExecutableStructure.set(*this, FunctionExecutable::createStructure(*this, 0, jsNull()));
 #if ENABLE(WEBASSEMBLY)
-    webAssemblyToJSCalleeStructure.set(*this, WebAssemblyToJSCallee::createStructure(*this, 0, jsNull()));
     webAssemblyCodeBlockStructure.set(*this, JSWebAssemblyCodeBlock::createStructure(*this, 0, jsNull()));
 #endif
     moduleProgramExecutableStructure.set(*this, ModuleProgramExecutable::createStructure(*this, 0, jsNull()));
index 761d7c0..6423188 100644 (file)
@@ -321,7 +321,6 @@ public:
     Strong<Structure> programExecutableStructure;
     Strong<Structure> functionExecutableStructure;
 #if ENABLE(WEBASSEMBLY)
-    Strong<Structure> webAssemblyToJSCalleeStructure;
     Strong<Structure> webAssemblyCodeBlockStructure;
 #endif
     Strong<Structure> moduleProgramExecutableStructure;
index 9f3d8d4..1efc51e 100644 (file)
@@ -267,6 +267,8 @@ private:
     GPRReg m_memorySizeGPR { InvalidGPRReg };
     GPRReg m_wasmContextGPR;
     Value* m_instanceValue; // FIXME: make this lazy https://bugs.webkit.org/show_bug.cgi?id=169792
+    bool m_makesCalls { false };
+    uint32_t m_maxNumJSCallArguments { 0 };
 };
 
 // Memory accesses in WebAssembly have unsigned 32-bit offsets, whereas they have signed 32-bit offsets in B3.
@@ -381,9 +383,52 @@ B3IRGenerator::B3IRGenerator(const ModuleInformation& info, Procedure& procedure
 
     wasmCallingConvention().setupFrameInPrologue(&compilation->wasmCalleeMoveLocation, m_proc, Origin(), m_currentBlock);
 
-    m_currentBlock = emitTierUpCheck(m_currentBlock, TierUpCount::functionEntryDecrement(), Origin());
-
     m_instanceValue = materializeWasmContext(m_currentBlock);
+
+    {
+        B3::Value* framePointer = m_currentBlock->appendNew<B3::Value>(m_proc, B3::FramePointer, Origin());
+        B3::PatchpointValue* stackOverflowCheck = m_currentBlock->appendNew<B3::PatchpointValue>(m_proc, B3::Void, Origin());
+        stackOverflowCheck->appendSomeRegister(framePointer);
+        stackOverflowCheck->appendSomeRegister(m_instanceValue);
+        stackOverflowCheck->clobber(RegisterSet::macroScratchRegisters());
+        stackOverflowCheck->numGPScratchRegisters = 2;
+        stackOverflowCheck->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
+            AllowMacroScratchRegisterUsage allowScratch(jit);
+            GPRReg fp = params[0].gpr();
+            GPRReg context = params[1].gpr();
+            GPRReg scratch1 = params.gpScratch(0);
+            GPRReg scratch2 = params.gpScratch(1);
+
+            const Checked<int32_t> wasmFrameSize = params.proc().frameSize();
+            const unsigned minimumParentCheckSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), 1024);
+            const unsigned extraFrameSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), std::max<uint32_t>(
+                // This allows us to elide stack checks for functions that are terminal nodes in the call
+                // tree, (e.g they don't make any calls) and have a small enough frame size. This works by
+                // having any such terminal node have its parent caller include some extra size in its
+                // own check for it. The goal here is twofold:
+                // 1. Emit less code.
+                // 2. Try to speed things up by skipping stack checks.
+                minimumParentCheckSize,
+                // This allows us to elide stack checks in the Wasm -> JS call IC stub. Since these will
+                // spill all arguments to the stack, we ensure that a stack check here covers the
+                // stack that such a stub would use.
+                (Checked<uint32_t>(m_maxNumJSCallArguments) * sizeof(Register) + jscCallingConvention().headerSizeInBytes()).unsafeGet()
+            ));
+            const int32_t checkSize = m_makesCalls ? (wasmFrameSize + extraFrameSize).unsafeGet() : wasmFrameSize.unsafeGet();
+            // This allows leaf functions to not do stack checks if their frame size is within
+            // certain limits since their caller would have already done the check.
+            if (m_makesCalls || wasmFrameSize >= minimumParentCheckSize) {
+                jit.loadPtr(CCallHelpers::Address(context, Context::offsetOfCachedStackLimit()), scratch2);
+                jit.addPtr(CCallHelpers::TrustedImm32(-checkSize), fp, scratch1);
+                auto overflow = jit.branchPtr(CCallHelpers::Below, scratch1, scratch2);
+                jit.addLinkTask([overflow] (LinkBuffer& linkBuffer) {
+                    linkBuffer.link(overflow, CodeLocationLabel(Thunks::singleton().stub(throwStackOverflowFromWasmThunkGenerator).code()));
+                });
+            }
+        });
+    }
+
+    m_currentBlock = emitTierUpCheck(m_currentBlock, TierUpCount::functionEntryDecrement(), Origin());
 }
 
 void B3IRGenerator::restoreWebAssemblyGlobalState(const MemoryInformation& memory, Value* instance, Procedure& proc, BasicBlock* block)
@@ -998,10 +1043,14 @@ auto B3IRGenerator::addCall(uint32_t functionIndex, const Signature& signature,
 {
     ASSERT(signature.argumentCount() == args.size());
 
+    m_makesCalls = true;
+
     Type returnType = signature.returnType();
     Vector<UnlinkedWasmToWasmCall>* unlinkedWasmToWasmCalls = &m_unlinkedWasmToWasmCalls;
 
     if (m_info.isImportedFunctionFromFunctionIndexSpace(functionIndex)) {
+        m_maxNumJSCallArguments = std::max(m_maxNumJSCallArguments, static_cast<uint32_t>(args.size()));
+
         // FIXME imports can be linked here, instead of generating a patchpoint, because all import stubs are generated before B3 compilation starts. https://bugs.webkit.org/show_bug.cgi?id=166462
         Value* functionImport = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), origin(), m_instanceValue, safeCast<int32_t>(JSWebAssemblyInstance::offsetOfImportFunction(functionIndex)));
         Value* jsTypeOfImport = m_currentBlock->appendNew<MemoryValue>(m_proc, Load8Z, origin(), functionImport, safeCast<int32_t>(JSCell::typeInfoTypeOffset()));
@@ -1017,7 +1066,9 @@ auto B3IRGenerator::addCall(uint32_t functionIndex, const Signature& signature,
                 patchpoint->effects.writesPinned = true;
                 patchpoint->effects.readsPinned = true;
                 // We need to clobber all potential pinned registers since we might be leaving the instance.
-                patchpoint->clobberLate(PinnedRegisterInfo::get().toSave());
+                // We pessimistically assume we could be calling to something that is bounds checking.
+                // FIXME: We shouldn't have to do this: https://bugs.webkit.org/show_bug.cgi?id=172181
+                patchpoint->clobberLate(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
                 patchpoint->setGenerator([unlinkedWasmToWasmCalls, functionIndex] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
                     AllowMacroScratchRegisterUsage allowScratch(jit);
                     CCallHelpers::Call call = jit.threadSafePatchableNearCall();
@@ -1043,7 +1094,9 @@ auto B3IRGenerator::addCall(uint32_t functionIndex, const Signature& signature,
                 patchpoint->effects.readsPinned = true;
                 patchpoint->append(jumpDestination, ValueRep::SomeRegister);
                 // We need to clobber all potential pinned registers since we might be leaving the instance.
-                patchpoint->clobberLate(PinnedRegisterInfo::get().toSave());
+                // We pessimistically assume we could be calling to something that is bounds checking.
+                // FIXME: We shouldn't have to do this: https://bugs.webkit.org/show_bug.cgi?id=172181
+                patchpoint->clobberLate(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
                 patchpoint->setGenerator([returnType] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
                     AllowMacroScratchRegisterUsage allowScratch(jit);
                     jit.call(params[returnType == Void ? 0 : 1].gpr());
@@ -1088,6 +1141,12 @@ auto B3IRGenerator::addCallIndirect(const Signature& signature, Vector<Expressio
     ExpressionType calleeIndex = args.takeLast();
     ASSERT(signature.argumentCount() == args.size());
 
+    m_makesCalls = true;
+    // Note: call indirect can call either WebAssemblyFunction or WebAssemblyWrapperFunction. Because
+    // WebAssemblyWrapperFunction is like calling into JS, we conservatively assume all call indirects
+    // can be to JS for our stack check calculation.
+    m_maxNumJSCallArguments = std::max(m_maxNumJSCallArguments, static_cast<uint32_t>(args.size()));
+
     ExpressionType callableFunctionBuffer;
     ExpressionType jsFunctionBuffer;
     ExpressionType callableFunctionBufferSize;
@@ -1168,14 +1227,17 @@ auto B3IRGenerator::addCallIndirect(const Signature& signature, Vector<Expressio
         patchpoint->clobber(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
         patchpoint->clobber(RegisterSet::macroScratchRegisters());
         patchpoint->append(newContext, ValueRep::SomeRegister);
+        patchpoint->append(m_instanceValue, ValueRep::SomeRegister);
         patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
             AllowMacroScratchRegisterUsage allowScratch(jit);
             GPRReg newContext = params[0].gpr();
+            GPRReg oldContext = params[1].gpr();
             const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
             const auto& sizeRegs = pinnedRegs.sizeRegisters;
             GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
             ASSERT(newContext != baseMemory);
-
+            jit.loadPtr(CCallHelpers::Address(oldContext, Context::offsetOfCachedStackLimit()), baseMemory);
+            jit.storePtr(baseMemory, CCallHelpers::Address(newContext, Context::offsetOfCachedStackLimit()));
             jit.storeWasmContext(newContext);
             jit.loadPtr(CCallHelpers::Address(newContext, Context::offsetOfMemory()), baseMemory); // JSWebAssemblyMemory*.
             ASSERT(sizeRegs.size() == 1);
@@ -1200,7 +1262,11 @@ auto B3IRGenerator::addCallIndirect(const Signature& signature, Vector<Expressio
             patchpoint->effects.writesPinned = true;
             patchpoint->effects.readsPinned = true;
             // We need to clobber all potential pinned registers since we might be leaving the instance.
-            patchpoint->clobberLate(PinnedRegisterInfo::get().toSave());
+            // We pessimistically assume we're always calling something that is bounds checking so
+            // because the wasm->wasm thunk unconditionally overrides the size registers.
+            // FIXME: We should not have to do this, but the wasm->wasm stub assumes it can
+            // use all the pinned registers as scratch: https://bugs.webkit.org/show_bug.cgi?id=172181
+            patchpoint->clobberLate(PinnedRegisterInfo::get().toSave(MemoryMode::BoundsChecking));
 
             patchpoint->append(calleeCode, ValueRep::SomeRegister);
             patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
index 1c73cf6..42a7849 100644 (file)
@@ -36,6 +36,7 @@
 #include "LinkBuffer.h"
 #include "NativeErrorConstructor.h"
 #include "WasmCallingConvention.h"
+#include "WasmContext.h"
 #include "WasmExceptionType.h"
 
 namespace JSC { namespace Wasm {
@@ -608,23 +609,29 @@ MacroAssemblerCodeRef wasmToWasm(unsigned importIndex)
     JIT jit;
 
     GPRReg scratch = GPRInfo::nonPreservedNonArgumentGPR;
+    GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
+    ASSERT(baseMemory != scratch);
+    const auto& sizeRegs = pinnedRegs.sizeRegisters;
+    ASSERT(sizeRegs.size() >= 1);
+    ASSERT(sizeRegs[0].sizeRegister != baseMemory);
+    ASSERT(sizeRegs[0].sizeRegister != scratch);
+    GPRReg sizeRegAsScratch = sizeRegs[0].sizeRegister;
 
+    static_assert(std::is_same<Context, JSWebAssemblyInstance>::value, "This is assumed in the code below.");
     // B3's call codegen ensures that the JSCell is a WebAssemblyFunction.
-    materializeImportJSCell(jit, importIndex, scratch);
+    jit.loadWasmContext(sizeRegAsScratch); // Old Instance*
+    jit.loadPtr(JIT::Address(sizeRegAsScratch, JSWebAssemblyInstance::offsetOfImportFunction(importIndex)), scratch);
 
     // Get the callee's WebAssembly.Instance and set it as WasmContext. The caller will take care of restoring its own Instance.
-    GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
-    ASSERT(baseMemory != scratch);
     jit.loadPtr(JIT::Address(scratch, WebAssemblyFunction::offsetOfInstance()), baseMemory); // Instance*.
     jit.storeWasmContext(baseMemory);
 
+    jit.loadPtr(JIT::Address(sizeRegAsScratch, JSWebAssemblyInstance::offsetOfCachedStackLimit()), sizeRegAsScratch);
+    jit.storePtr(sizeRegAsScratch, JIT::Address(baseMemory, JSWebAssemblyInstance::offsetOfCachedStackLimit()));
+
     // FIXME the following code assumes that all WebAssembly.Instance have the same pinned registers. https://bugs.webkit.org/show_bug.cgi?id=162952
     // Set up the callee's baseMemory register as well as the memory size registers.
     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyInstance::offsetOfMemory()), baseMemory); // JSWebAssemblyMemory*.
-    const auto& sizeRegs = pinnedRegs.sizeRegisters;
-    ASSERT(sizeRegs.size() >= 1);
-    ASSERT(sizeRegs[0].sizeRegister != baseMemory);
-    ASSERT(sizeRegs[0].sizeRegister != scratch);
     ASSERT(!sizeRegs[0].sizeOffset); // The following code assumes we start at 0, and calculates subsequent size registers relative to 0.
     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyMemory::offsetOfSize()), sizeRegs[0].sizeRegister); // Memory size.
     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyMemory::offsetOfMemory()), baseMemory); // WasmMemory::void*.
index 998b6aa..fea16de 100644 (file)
@@ -40,7 +40,6 @@ Context* loadContext(VM& vm)
     if (useFastTLSForContext())
         return bitwise_cast<Context*>(_pthread_getspecific_direct(WTF_WASM_CONTEXT_KEY));
 #endif
-    // FIXME: Save this state elsewhere to allow PIC. https://bugs.webkit.org/show_bug.cgi?id=169773
     return vm.wasmContext;
 }
 
@@ -50,8 +49,9 @@ void storeContext(VM& vm, Context* context)
     if (useFastTLSForContext())
         _pthread_setspecific_direct(WTF_WASM_CONTEXT_KEY, bitwise_cast<void*>(context));
 #endif
-    // FIXME: Save this state elsewhere to allow PIC. https://bugs.webkit.org/show_bug.cgi?id=169773
     vm.wasmContext = context;
+    if (context)
+        context->setCachedStackLimit(vm.softStackLimit());
 }
 
 } } // namespace JSC::Wasm
index 5f0b327..3cf65fb 100644 (file)
@@ -51,7 +51,9 @@ inline bool useFastTLS()
 
 inline bool useFastTLSForContext()
 {
-    return useFastTLS();
+    if (useFastTLS())
+        return Options::useFastTLSForWasmContext();
+    return false;
 }
 
 Context* loadContext(VM&);
index 34bb3d2..28bdf56 100644 (file)
@@ -39,7 +39,8 @@ namespace Wasm {
     macro(OutOfBoundsTrunc, "Out of bounds Trunc operation") \
     macro(Unreachable, "Unreachable code should not be executed") \
     macro(DivisionByZero, "Division by zero") \
-    macro(IntegerOverflow, "Integer overflow")
+    macro(IntegerOverflow, "Integer overflow") \
+    macro(StackOverflow, "Stack overflow")
 
 enum class ExceptionType : uint32_t {
 #define MAKE_ENUM(enumName, error) enumName,
index eae1037..4237bac 100644 (file)
@@ -48,7 +48,7 @@ struct PinnedRegisterInfo {
     static const PinnedRegisterInfo& get();
     PinnedRegisterInfo(Vector<PinnedSizeRegisterInfo>&&, GPRReg, GPRReg);
 
-    RegisterSet toSave(MemoryMode mode = MemoryMode::BoundsChecking) const
+    RegisterSet toSave(MemoryMode mode) const
     {
         RegisterSet result;
         result.set(baseMemoryPointer);
index 2f1cc41..17e8ef4 100644 (file)
@@ -40,7 +40,7 @@
 
 namespace JSC { namespace Wasm {
 
-MacroAssemblerCodeRef throwExceptionFromWasmThunkGenerator()
+MacroAssemblerCodeRef throwExceptionFromWasmThunkGenerator(const AbstractLocker&)
 {
     CCallHelpers jit;
 
@@ -62,8 +62,11 @@ MacroAssemblerCodeRef throwExceptionFromWasmThunkGenerator()
             auto throwScope = DECLARE_THROW_SCOPE(*vm);
             JSGlobalObject* globalObject = wasmContext->globalObject();
 
-            JSWebAssemblyRuntimeError* error = JSWebAssemblyRuntimeError::create(
-                exec, *vm, globalObject->WebAssemblyRuntimeErrorStructure(), Wasm::errorMessageForExceptionType(type));
+            JSObject* error; 
+            if (type == ExceptionType::StackOverflow)
+                error = createStackOverflowError(exec, globalObject);
+            else
+                error = JSWebAssemblyRuntimeError::create(exec, *vm, globalObject->WebAssemblyRuntimeErrorStructure(), Wasm::errorMessageForExceptionType(type));
             throwException(exec, throwScope, error);
         }
 
@@ -86,6 +89,18 @@ MacroAssemblerCodeRef throwExceptionFromWasmThunkGenerator()
     return FINALIZE_CODE(linkBuffer, ("Throw exception from Wasm"));
 }
 
+MacroAssemblerCodeRef throwStackOverflowFromWasmThunkGenerator(const AbstractLocker& locker)
+{
+    CCallHelpers jit;
+
+    jit.move(GPRInfo::callFrameRegister, MacroAssembler::stackPointerRegister);
+    jit.move(CCallHelpers::TrustedImm32(static_cast<uint32_t>(ExceptionType::StackOverflow)), GPRInfo::argumentGPR1);
+    auto jumpToExceptionHandler = jit.jump();
+    LinkBuffer linkBuffer(jit, GLOBAL_THUNK_ID);
+    linkBuffer.link(jumpToExceptionHandler, CodeLocationLabel(Thunks::singleton().stub(locker, throwExceptionFromWasmThunkGenerator).code()));
+    return FINALIZE_CODE(linkBuffer, ("Throw stack overflow from Wasm"));
+}
+
 static Thunks* thunks;
 void Thunks::initialize()
 {
@@ -101,12 +116,22 @@ Thunks& Thunks::singleton()
 MacroAssemblerCodeRef Thunks::stub(ThunkGenerator generator)
 {
     auto locker = holdLock(m_lock);
+    return stub(locker, generator);
+}
 
+MacroAssemblerCodeRef Thunks::stub(const AbstractLocker& locker, ThunkGenerator generator)
+{
     ASSERT(!!generator);
-    auto addResult = m_stubs.add(generator, MacroAssemblerCodeRef());
-    if (addResult.isNewEntry)
-        addResult.iterator->value = generator();
-    return addResult.iterator->value;
+    {
+        auto addResult = m_stubs.add(generator, MacroAssemblerCodeRef());
+        if (!addResult.isNewEntry)
+            return addResult.iterator->value;
+    }
+
+    MacroAssemblerCodeRef code = generator(locker);
+    // We specifically don't use the iterator here to allow generator to recursively change m_stubs.
+    m_stubs.set(generator, code);
+    return code;
 }
 
 MacroAssemblerCodeRef Thunks::existingStub(ThunkGenerator generator)
index 65375d6..6bca302 100644 (file)
 
 namespace JSC { namespace Wasm {
 
-MacroAssemblerCodeRef throwExceptionFromWasmThunkGenerator();
+MacroAssemblerCodeRef throwExceptionFromWasmThunkGenerator(const AbstractLocker&);
+MacroAssemblerCodeRef throwStackOverflowFromWasmThunkGenerator(const AbstractLocker&);
 
-typedef MacroAssemblerCodeRef (*ThunkGenerator)();
+typedef MacroAssemblerCodeRef (*ThunkGenerator)(const AbstractLocker&);
 
 class Thunks {
 public:
@@ -41,6 +42,7 @@ public:
     static Thunks& singleton();
 
     MacroAssemblerCodeRef stub(ThunkGenerator);
+    MacroAssemblerCodeRef stub(const AbstractLocker&, ThunkGenerator);
     MacroAssemblerCodeRef existingStub(ThunkGenerator);
 
 private:
index f02ac6d..23df6cf 100644 (file)
@@ -73,11 +73,15 @@ public:
     static ptrdiff_t offsetOfGlobals() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_globals); }
     static ptrdiff_t offsetOfVM() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_vm); }
     static ptrdiff_t offsetOfCodeBlock() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_codeBlock); }
+    static ptrdiff_t offsetOfCachedStackLimit() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_cachedStackLimit); }
     static size_t offsetOfImportFunctions() { return WTF::roundUpToMultipleOf<sizeof(WriteBarrier<JSCell>)>(sizeof(JSWebAssemblyInstance)); }
     static size_t offsetOfImportFunction(size_t importFunctionNum) { return offsetOfImportFunctions() + importFunctionNum * sizeof(sizeof(WriteBarrier<JSCell>)); }
 
     WebAssemblyToJSCallee* webAssemblyToJSCallee() { return m_callee.get(); }
 
+    void* cachedStackLimit() const { return m_cachedStackLimit; }
+    void setCachedStackLimit(void* limit) { m_cachedStackLimit = limit; }
+
 protected:
     JSWebAssemblyInstance(VM&, Structure*, unsigned numImportFunctions);
     void finishCreation(VM&, JSWebAssemblyModule*, JSModuleNamespaceObject*);
@@ -101,6 +105,7 @@ private:
     WriteBarrier<JSWebAssemblyTable> m_table;
     WriteBarrier<WebAssemblyToJSCallee> m_callee;
     MallocPtr<uint64_t> m_globals;
+    void* m_cachedStackLimit { bitwise_cast<void*>(std::numeric_limits<uintptr_t>::max()) };
     unsigned m_numImportFunctions;
 };
 
index 4ac4983..c0726ff 100644 (file)
@@ -82,7 +82,7 @@ void JSWebAssemblyModule::finishCreation(VM& vm)
     }
 
     m_exportSymbolTable.set(vm, this, exportSymbolTable);
-    m_callee.set(vm, this, WebAssemblyToJSCallee::create(vm, vm.webAssemblyToJSCalleeStructure.get(), this));
+    m_callee.set(vm, this, WebAssemblyToJSCallee::create(vm, this));
 }
 
 void JSWebAssemblyModule::destroy(JSCell* cell)
index e2800bf..8747db3 100644 (file)
@@ -128,6 +128,15 @@ static EncodedJSValue JSC_HOST_CALL callWebAssemblyFunction(ExecState* exec)
 
     // FIXME Do away with this entire function, and only use the entrypoint generated by B3. https://bugs.webkit.org/show_bug.cgi?id=166486
     Wasm::Context* prevWasmContext = Wasm::loadContext(vm);
+    {
+        // We do the stack check here for the wrapper function because we don't
+        // want to emit a stack check inside every wrapper function.
+        const intptr_t sp = bitwise_cast<intptr_t>(&sp); // A proxy for the current stack pointer.
+        const intptr_t frameSize = (boxedArgs.size() + CallFrame::headerSizeInRegisters) * sizeof(Register);
+        const intptr_t stackSpaceUsed = 2 * frameSize; // We're making two calls. One to the wrapper, and one to the actual wasm code.
+        if (UNLIKELY((sp - stackSpaceUsed) < bitwise_cast<intptr_t>(vm.softStackLimit())))
+            return JSValue::encode(throwException(exec, scope, createStackOverflowError(exec)));
+    }
     Wasm::storeContext(vm, wasmContext);
     ASSERT(wasmFunction->instance());
     ASSERT(wasmFunction->instance() == Wasm::loadContext(vm));
@@ -135,7 +144,14 @@ static EncodedJSValue JSC_HOST_CALL callWebAssemblyFunction(ExecState* exec)
     // We need to make sure this is in a register or on the stack since it's stored in Vector<JSValue>.
     // This probably isn't strictly necessary, since the WebAssemblyFunction* should keep the instance
     // alive. But it's good hygiene.
-    wasmContext->use(); 
+    wasmContext->use();
+    if (prevWasmContext != wasmContext) {
+        // This is just for some extra safety instead of leaving a cached
+        // value in there. If we ever forget to set the value to be a real
+        // bounds, this will force every stack overflow check to immediately
+        // fire.
+        wasmContext->setCachedStackLimit(bitwise_cast<void*>(std::numeric_limits<uintptr_t>::max()));
+    }
     Wasm::storeContext(vm, prevWasmContext);
     RETURN_IF_EXCEPTION(scope, { });
 
index 4b9c86f..057c579 100644 (file)
 
 namespace JSC {
 
-const ClassInfo WebAssemblyToJSCallee::s_info = { "WebAssemblyToJSCallee", nullptr, 0, CREATE_METHOD_TABLE(WebAssemblyToJSCallee) };
+const ClassInfo WebAssemblyToJSCallee::s_info = { "WebAssemblyToJSCallee", &Base::s_info, 0, CREATE_METHOD_TABLE(WebAssemblyToJSCallee) };
 
-WebAssemblyToJSCallee* WebAssemblyToJSCallee::create(VM& vm, Structure* structure, JSWebAssemblyModule* module)
+WebAssemblyToJSCallee* WebAssemblyToJSCallee::create(VM& vm, JSWebAssemblyModule* module)
 {
+    Structure* structure = module->globalObject()->webAssemblyToJSCalleeStructure();
     WebAssemblyToJSCallee* callee = new (NotNull, allocateCell<WebAssemblyToJSCallee>(vm.heap)) WebAssemblyToJSCallee(vm, structure);
     callee->finishCreation(vm, module);
     return callee;
index 19f4795..061d14d 100644 (file)
 
 #if ENABLE(WEBASSEMBLY)
 
-#include "JSCell.h"
+#include "JSObject.h"
 
 namespace JSC {
 
 class JSWebAssemblyModule;
 
-class WebAssemblyToJSCallee final : public JSCell {
+class WebAssemblyToJSCallee final : public JSNonFinalObject {
 public:
-    typedef JSCell Base;
-    static const unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;
+    using Base = JSNonFinalObject;
+    static const unsigned StructureFlags = Base::StructureFlags;
 
-    static WebAssemblyToJSCallee* create(VM&, Structure*, JSWebAssemblyModule*);
+    static WebAssemblyToJSCallee* create(VM&, JSWebAssemblyModule*);
     static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
 
     DECLARE_EXPORT_INFO;
index 0917a55..80cc961 100644 (file)
@@ -1,3 +1,15 @@
+2017-05-18  Saam Barati  <sbarati@apple.com>
+
+        WebAssembly: perform stack checks
+        https://bugs.webkit.org/show_bug.cgi?id=165546
+        <rdar://problem/29760307>
+
+        Reviewed by Filip Pizlo.
+
+        Add some new testing modes for using and not using fast TLS wasm contexts.
+
+        * Scripts/run-jsc-stress-tests:
+
 2017-05-18  Daniel Bates  <dabates@apple.com>
 
         REGRESSION (r209608): Cross-origin plugin document opened in child window blocked by parent
index d88b4fc..8c44857 100755 (executable)
@@ -1199,9 +1199,10 @@ def runWebAssembly
     prepareExtraAbsoluteFiles(WASMTESTS_PATH, ["wasm.json"])
     prepareExtraRelativeFiles(modules.map { |f| "../" + f }, $collection)
     run("default-wasm", "-m", *FTL_OPTIONS)
-    run("wasm-no-cjit", "-m", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
+    run("wasm-no-cjit-yes-tls-context", "-m", "--useFastTLSForWasmContext=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
     run("wasm-eager-jettison", "-m", "--forceCodeBlockToJettisonDueToOldAge=true", *FTL_OPTIONS)
     run("wasm-no-call-ic", "-m", "--useCallICsForWebAssemblyToJSCalls=false", *FTL_OPTIONS)
+    run("wasm-no-tls-context", "-m", "--useFastTLSForWasmContext=false", *FTL_OPTIONS)
 end
 
 def runWebAssemblyEmscripten(mode)
@@ -1214,9 +1215,10 @@ def runWebAssemblyEmscripten(mode)
     wasm = $benchmark.to_s.sub! '.js', '.wasm'
     prepareExtraRelativeFiles([Pathname('..') + wasm], $collection)
     run("default-wasm", *FTL_OPTIONS)
-    run("wasm-no-cjit", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
+    run("wasm-no-cjit-yes-tls-context", "--useFastTLSForWasmContext=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
     run("wasm-eager-jettison", "--forceCodeBlockToJettisonDueToOldAge=true", *FTL_OPTIONS)
     run("wasm-no-call-ic", "--useCallICsForWebAssemblyToJSCalls=false", *FTL_OPTIONS)
+    run("wasm-no-tls-context", "--useFastTLSForWasmContext=false", *FTL_OPTIONS)
 end
 
 def runWebAssemblySpecTest(mode)
@@ -1236,9 +1238,10 @@ def runWebAssemblySpecTest(mode)
     prepareExtraRelativeFiles(harness.map { |f| "../../spec-harness/" + f }, $collection)
 
     runWithOutputHandler("default-wasm", noisyOutputHandler, "../spec-harness.js", *FTL_OPTIONS)
-    runWithOutputHandler("wasm-no-cjit", noisyOutputHandler, "../spec-harness.js", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
+    runWithOutputHandler("wasm-no-cjit-yes-tls-context", noisyOutputHandler, "../spec-harness.js",  "--useFastTLSForWasmContext=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
     runWithOutputHandler("wasm-eager-jettison", noisyOutputHandler, "../spec-harness.js", "--forceCodeBlockToJettisonDueToOldAge=true", *FTL_OPTIONS)
     runWithOutputHandler("wasm-no-call-ic", noisyOutputHandler, "../spec-harness.js", "--useCallICsForWebAssemblyToJSCalls=false", *FTL_OPTIONS)
+    runWithOutputHandler("wasm-no-tls-context", noisyOutputHandler, "../spec-harness.js", "--useFastTLSForWasmContext=false", *FTL_OPTIONS)
 end
 
 def runChakra(mode, exception, baselineFile, extraFiles)