Add support for WASM calls
authorkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Oct 2016 16:02:39 +0000 (16:02 +0000)
committerkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Oct 2016 16:02:39 +0000 (16:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=161727

Reviewed by Filip Pizlo and Michael Saboff.

JSTests:

Add members of the Call category to the WASMOps special group.

* wasm/generate-wasmops-header.js:

Source/JavaScriptCore:

Add support for WASM calls. Since most of the work for this was already done when we added
WASM Memory, this is mostly just cleanup work.  The main interesting part of this patch is
how we link calls to other WASM functions in the same module. Since a WASM callee may not
have been compiled by the time the current function has started compilation we don't know
what address we need to call to.  For each callsite in the compiling function, WASM
remembers the CodeLocationCall and the target function index. Once all WASM functions are
compiled, each callsite is linked to the appropriate entrypoint.

* testWASM.cpp:
(runWASMTests):
* wasm/WASMB3IRGenerator.cpp:
(JSC::WASM::createJSWrapper):
(JSC::WASM::parseAndCompile):
* wasm/WASMB3IRGenerator.h:
* wasm/WASMCallingConvention.cpp:
(JSC::WASM::jscCallingConvention):
(JSC::WASM::wasmCallingConvention):
* wasm/WASMCallingConvention.h:
(JSC::WASM::CallingConvention::CallingConvention):
(JSC::WASM::CallingConvention::marshallArgumentImpl):
(JSC::WASM::CallingConvention::marshallArgument):
(JSC::WASM::CallingConvention::loadArguments):
(JSC::WASM::CallingConvention::setupCall):
(JSC::WASM::CallingConvention::iterate): Deleted.
* wasm/WASMFormat.h:
* wasm/WASMFunctionParser.h:
(JSC::WASM::FunctionParser<Context>::FunctionParser):
(JSC::WASM::FunctionParser<Context>::parseBlock):
(JSC::WASM::FunctionParser<Context>::parseExpression):
* wasm/WASMModuleParser.cpp:
(JSC::WASM::ModuleParser::parse):
* wasm/WASMOps.h:
* wasm/WASMParser.h:
(JSC::WASM::Parser::parseVarUInt32):
(JSC::WASM::Parser::parseVarUInt64):
* wasm/WASMPlan.cpp:
(JSC::WASM::Plan::Plan):

Source/WTF:

Added a new decodeUInt64. Also, added WTF::LEBDecoder namespace.

* wtf/LEBDecoder.h:
(WTF::LEBDecoder::decodeUInt):
(WTF::LEBDecoder::decodeUInt32):
(WTF::LEBDecoder::decodeUInt64):
(WTF::LEBDecoder::decodeInt32):
(decodeUInt32): Deleted.
(decodeInt32): Deleted.

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

16 files changed:
JSTests/ChangeLog
JSTests/wasm/generate-wasmops-header.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/testWASM.cpp
Source/JavaScriptCore/wasm/WASMB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WASMB3IRGenerator.h
Source/JavaScriptCore/wasm/WASMCallingConvention.cpp
Source/JavaScriptCore/wasm/WASMCallingConvention.h
Source/JavaScriptCore/wasm/WASMFormat.h
Source/JavaScriptCore/wasm/WASMFunctionParser.h
Source/JavaScriptCore/wasm/WASMModuleParser.cpp
Source/JavaScriptCore/wasm/WASMOps.h
Source/JavaScriptCore/wasm/WASMParser.h
Source/JavaScriptCore/wasm/WASMPlan.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/LEBDecoder.h

index 08ed3c1..ed1fbb8 100644 (file)
@@ -1,3 +1,14 @@
+2016-10-20  Keith Miller  <keith_miller@apple.com>
+
+        Add support for WASM calls
+        https://bugs.webkit.org/show_bug.cgi?id=161727
+
+        Reviewed by Filip Pizlo and Michael Saboff.
+
+        Add members of the Call category to the WASMOps special group.
+
+        * wasm/generate-wasmops-header.js:
+
 2016-10-20  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Drop isEnvironmentRecord type info flag and use JSType information instead
index 7385bbf..b9a4252 100644 (file)
@@ -29,7 +29,7 @@ function* opcodeMacroizer(filter) {
 
 const defines = [
     "#define FOR_EACH_WASM_SPECIAL_OP(macro)",
-    ...opcodeMacroizer(op => op.category === "special"),
+    ...opcodeMacroizer(op => op.category === "special" || op.category === "call"),
     "\n\n#define FOR_EACH_WASM_CONTROL_FLOW_OP(macro)",
     ...opcodeMacroizer(op => op.category === "control"),
     "\n\n#define FOR_EACH_WASM_UNARY_OP(macro)",
index 760db37..4c1164f 100644 (file)
@@ -1,3 +1,48 @@
+2016-10-20  Keith Miller  <keith_miller@apple.com>
+
+        Add support for WASM calls
+        https://bugs.webkit.org/show_bug.cgi?id=161727
+
+        Reviewed by Filip Pizlo and Michael Saboff.
+
+        Add support for WASM calls. Since most of the work for this was already done when we added
+        WASM Memory, this is mostly just cleanup work.  The main interesting part of this patch is
+        how we link calls to other WASM functions in the same module. Since a WASM callee may not
+        have been compiled by the time the current function has started compilation we don't know
+        what address we need to call to.  For each callsite in the compiling function, WASM
+        remembers the CodeLocationCall and the target function index. Once all WASM functions are
+        compiled, each callsite is linked to the appropriate entrypoint.
+
+        * testWASM.cpp:
+        (runWASMTests):
+        * wasm/WASMB3IRGenerator.cpp:
+        (JSC::WASM::createJSWrapper):
+        (JSC::WASM::parseAndCompile):
+        * wasm/WASMB3IRGenerator.h:
+        * wasm/WASMCallingConvention.cpp:
+        (JSC::WASM::jscCallingConvention):
+        (JSC::WASM::wasmCallingConvention):
+        * wasm/WASMCallingConvention.h:
+        (JSC::WASM::CallingConvention::CallingConvention):
+        (JSC::WASM::CallingConvention::marshallArgumentImpl):
+        (JSC::WASM::CallingConvention::marshallArgument):
+        (JSC::WASM::CallingConvention::loadArguments):
+        (JSC::WASM::CallingConvention::setupCall):
+        (JSC::WASM::CallingConvention::iterate): Deleted.
+        * wasm/WASMFormat.h:
+        * wasm/WASMFunctionParser.h:
+        (JSC::WASM::FunctionParser<Context>::FunctionParser):
+        (JSC::WASM::FunctionParser<Context>::parseBlock):
+        (JSC::WASM::FunctionParser<Context>::parseExpression):
+        * wasm/WASMModuleParser.cpp:
+        (JSC::WASM::ModuleParser::parse):
+        * wasm/WASMOps.h:
+        * wasm/WASMParser.h:
+        (JSC::WASM::Parser::parseVarUInt32):
+        (JSC::WASM::Parser::parseVarUInt64):
+        * wasm/WASMPlan.cpp:
+        (JSC::WASM::Plan::Plan):
+
 2016-10-21  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Implement InputEvent.getTargetRanges() for the input events spec
index 2c3c9fa..0576ec4 100644 (file)
@@ -135,7 +135,7 @@ StaticLock crashLock;
         Vector<uint8_t> vector = Vector<uint8_t> init; \
         size_t offset = startOffset; \
         uint32_t result; \
-        bool status = decodeUInt32(vector.data(), vector.size(), offset, result); \
+        bool status = WTF::LEBDecoder::decodeUInt32(vector.data(), vector.size(), offset, result); \
         CHECK_EQ(status, expectedStatus); \
         if (expectedStatus) { \
             CHECK_EQ(result, expectedResult); \
@@ -183,7 +183,7 @@ StaticLock crashLock;
         Vector<uint8_t> vector = Vector<uint8_t> init; \
         size_t offset = startOffset; \
         int32_t result; \
-        bool status = decodeInt32(vector.data(), vector.size(), offset, result); \
+        bool status = WTF::LEBDecoder::decodeInt32(vector.data(), vector.size(), offset, result); \
         CHECK_EQ(status, expectedStatus); \
         if (expectedStatus) { \
             int32_t expected = expectedResult; \
@@ -244,6 +244,136 @@ static void runWASMTests()
 {
     {
         // Generated from:
+        //    (module
+        //     (memory 1)
+        //     (func $sum12 (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32) (return (i32.add (get_local 0) (i32.add (get_local 1) (i32.add (get_local 2) (i32.add (get_local 3) (i32.add (get_local 4) (i32.add (get_local 5) (i32.add (get_local 6) (i32.add (get_local 7) (i32.add (get_local 8) (i32.add (get_local 9) (i32.add (get_local 10) (get_local 11))))))))))))))
+        //     (func (export "mult12") (param i32) (result i32) (return (call $sum12 (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0) (get_local 0))))
+        //     )
+
+        Vector<uint8_t> vector = {
+            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x96, 0x80, 0x80, 0x80, 0x00, 0x02, 0x40,
+            0x0c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x40,
+            0x01, 0x01, 0x01, 0x01, 0x03, 0x83, 0x80, 0x80, 0x80, 0x00, 0x02, 0x00, 0x01, 0x05, 0x83, 0x80,
+            0x80, 0x80, 0x00, 0x01, 0x00, 0x01, 0x07, 0x8a, 0x80, 0x80, 0x80, 0x00, 0x01, 0x06, 0x6d, 0x75,
+            0x6c, 0x74, 0x31, 0x32, 0x00, 0x01, 0x0a, 0xce, 0x80, 0x80, 0x80, 0x00, 0x02, 0xa6, 0x80, 0x80,
+            0x80, 0x00, 0x00, 0x14, 0x00, 0x14, 0x01, 0x14, 0x02, 0x14, 0x03, 0x14, 0x04, 0x14, 0x05, 0x14,
+            0x06, 0x14, 0x07, 0x14, 0x08, 0x14, 0x09, 0x14, 0x0a, 0x14, 0x0b, 0x40, 0x40, 0x40, 0x40, 0x40,
+            0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x09, 0x0f, 0x9d, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x00,
+            0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00,
+            0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x16, 0x00, 0x09, 0x0f
+        };
+
+        Plan plan(*vm, vector);
+        if (plan.result.size() != 2 || !plan.result[0] || !plan.result[1]) {
+            dataLogLn("Module failed to compile correctly.");
+            CRASH();
+        }
+
+        // Test this doesn't crash.
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(100) }), 1200);
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(1) }), 12);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(1), box(2), box(3), box(4), box(5), box(6), box(7), box(8), box(9), box(10), box(11), box(12) }), 78);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(1), box(2), box(3), box(4), box(5), box(6), box(7), box(8), box(9), box(10), box(11), box(100) }), 166);
+    }
+
+    {
+        // Generated from:
+        //    (module
+        //     (memory 1)
+        //     (func $fac (export "fac") (param i64) (result i64)
+        //      (if (i64.eqz (get_local 0))
+        //       (return (i64.const 1))
+        //       )
+        //      (return (i64.mul (get_local 0) (call $fac (i64.sub (get_local 0) (i64.const 1)))))
+        //      )
+        //     )
+        Vector<uint8_t> vector = {
+            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x86, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
+            0x01, 0x02, 0x01, 0x02, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x05, 0x83, 0x80, 0x80,
+            0x80, 0x00, 0x01, 0x00, 0x01, 0x07, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x03, 0x66, 0x61, 0x63,
+            0x00, 0x00, 0x0a, 0x9e, 0x80, 0x80, 0x80, 0x00, 0x01, 0x98, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14,
+            0x00, 0x11, 0x00, 0x68, 0x03, 0x00, 0x11, 0x01, 0x09, 0x0f, 0x14, 0x00, 0x14, 0x00, 0x11, 0x01,
+            0x5c, 0x16, 0x00, 0x5d, 0x09, 0x0f
+        };
+
+        Plan plan(*vm, vector);
+        if (plan.result.size() != 1 || !plan.result[0]) {
+            dataLogLn("Module failed to compile correctly.");
+            CRASH();
+        }
+
+        // Test this doesn't crash.
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(0) }), 1);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(2) }), 2);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(4) }), 24);
+    }
+
+    {
+        // Generated from:
+        //    (module
+        //     (memory 1)
+        //     (func (export "double") (param i64) (result i64) (return (call 1 (get_local 0) (get_local 0))))
+        //     (func $sum (param i64) (param i64) (result i64) (return (i64.add (get_local 0) (get_local 1))))
+        //     )
+        Vector<uint8_t> vector = {
+            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x8c, 0x80, 0x80, 0x80, 0x00, 0x02, 0x40,
+            0x01, 0x02, 0x01, 0x02, 0x40, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x83, 0x80, 0x80, 0x80, 0x00,
+            0x02, 0x00, 0x01, 0x05, 0x83, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x01, 0x07, 0x8a, 0x80, 0x80,
+            0x80, 0x00, 0x01, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x00, 0x00, 0x0a, 0x9c, 0x80, 0x80,
+            0x80, 0x00, 0x02, 0x89, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x16, 0x01, 0x09,
+            0x0f, 0x88, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x00, 0x14, 0x01, 0x5b, 0x09, 0x0f
+        };
+
+        Plan plan(*vm, vector);
+        if (plan.result.size() != 2 || !plan.result[0] || !plan.result[1]) {
+            dataLogLn("Module failed to compile correctly.");
+            CRASH();
+        }
+
+        // Test this doesn't crash.
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(0), box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(100), box(0) }), 100);
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(1), box(15) }), 16);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(100) }), 200);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(1) }), 2);
+    }
+
+    {
+        // Generated from:
+        //    (module
+        //     (memory 1)
+        //     (func $id (param $value i32) (result i32) (return (get_local $value)))
+        //     (func (export "id-call") (param $value i32) (result i32) (return (call $id (get_local $value))))
+        //     )
+        Vector<uint8_t> vector = {
+            0x00, 0x61, 0x73, 0x6d, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x86, 0x80, 0x80, 0x80, 0x00, 0x01, 0x40,
+            0x01, 0x01, 0x01, 0x01, 0x03, 0x83, 0x80, 0x80, 0x80, 0x00, 0x02, 0x00, 0x00, 0x05, 0x83, 0x80,
+            0x80, 0x80, 0x00, 0x01, 0x01, 0x01, 0x07, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x01, 0x07, 0x69, 0x64,
+            0x2d, 0x63, 0x61, 0x6c, 0x6c, 0x00, 0x01, 0x0a, 0x97, 0x80, 0x80, 0x80, 0x00, 0x02, 0x85, 0x80,
+            0x80, 0x80, 0x00, 0x00, 0x14, 0x00, 0x09, 0x0f, 0x87, 0x80, 0x80, 0x80, 0x00, 0x00, 0x14, 0x00,
+            0x16, 0x00, 0x09, 0x0f
+        };
+
+        Plan plan(*vm, vector);
+        if (plan.result.size() != 2 || !plan.result[0] || !plan.result[1]) {
+            dataLogLn("Module failed to compile correctly.");
+            CRASH();
+        }
+
+        // Test this doesn't crash.
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(100) }), 100);
+        CHECK_EQ(invoke<int>(*plan.result[1]->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(100) }), 100);
+        CHECK_EQ(invoke<int>(*plan.result[0]->jsEntryPoint, { box(1) }), 1);
+    }
+
+    {
+        // Generated from:
         // (module
         //  (memory 1)
         //  (func (export "i32_load8_s") (param $i i32) (param $ptr i32) (result i32)
index 686551c..b4a800c 100644 (file)
@@ -170,7 +170,7 @@ public:
     typedef Vector<Variable*, 1> ResultList;
     static constexpr ExpressionType emptyExpression = nullptr;
 
-    B3IRGenerator(Memory*, Procedure&);
+    B3IRGenerator(Memory*, Procedure&, Vector<UnlinkedCall>& unlinkedCalls);
 
     void addArguments(const Vector<Type>&);
     void addLocal(Type, uint32_t);
@@ -198,6 +198,8 @@ public:
     bool WARN_UNUSED_RETURN addBranch(ControlData&, ExpressionType condition, const ExpressionList& returnValues);
     bool WARN_UNUSED_RETURN endBlock(ControlData&, ExpressionList& expressionStack);
 
+    bool WARN_UNUSED_RETURN addCall(unsigned calleeIndex, const FunctionInformation&, Vector<ExpressionType>& args, ExpressionType& result);
+
     bool isContinuationReachable(ControlData&);
 
     void dump(const Vector<ControlType>& controlStack, const ExpressionList& expressionStack);
@@ -214,13 +216,16 @@ private:
     Procedure& m_proc;
     BasicBlock* m_currentBlock;
     Vector<Variable*> m_locals;
+    // m_unlikedCalls is list of each call site and the function index whose address it should be patched with.
+    Vector<UnlinkedCall>& m_unlinkedCalls;
     GPRReg m_memoryBaseGPR;
     GPRReg m_memorySizeGPR;
 };
 
-B3IRGenerator::B3IRGenerator(Memory* memory, Procedure& procedure)
+B3IRGenerator::B3IRGenerator(Memory* memory, Procedure& procedure, Vector<UnlinkedCall>& unlinkedCalls)
     : m_memory(memory)
     , m_proc(procedure)
+    , m_unlinkedCalls(unlinkedCalls)
 {
     m_currentBlock = m_proc.addBlock();
 
@@ -231,13 +236,13 @@ B3IRGenerator::B3IRGenerator(Memory* memory, Procedure& procedure)
         m_memorySizeGPR = m_memory->pinnedRegisters().sizeRegisters[0].sizeRegister;
         for (const PinnedSizeRegisterInfo& info : m_memory->pinnedRegisters().sizeRegisters)
             m_proc.pinRegister(info.sizeRegister);
-    }
 
-    m_proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR, unsigned) {
-        ASSERT_UNUSED(pinnedGPR, m_memorySizeGPR == pinnedGPR);
-        // FIXME: This should unwind the stack and throw a JS exception. See: https://bugs.webkit.org/show_bug.cgi?id=163351
-        jit.breakpoint();
-    });
+        m_proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR, unsigned) {
+            ASSERT_UNUSED(pinnedGPR, m_memorySizeGPR == pinnedGPR);
+            // FIXME: This should unwind the stack and throw a JS exception. See: https://bugs.webkit.org/show_bug.cgi?id=163351
+            jit.breakpoint();
+        });
+    }
 }
 
 void B3IRGenerator::addLocal(Type type, uint32_t count)
@@ -251,7 +256,7 @@ void B3IRGenerator::addArguments(const Vector<Type>& types)
 {
     ASSERT(!m_locals.size());
     m_locals.grow(types.size());
-    jscCallingConvention().iterate(types, m_proc, m_currentBlock, Origin(),
+    wasmCallingConvention().loadArguments(types, m_proc, m_currentBlock, Origin(),
         [&] (ExpressionType argument, unsigned i) {
             Variable* argumentVariable = m_proc.addVariable(argument->type());
             m_locals[i] = argumentVariable;
@@ -561,6 +566,32 @@ bool B3IRGenerator::endBlock(ControlData& data, ExpressionList& expressionStack)
     return true;
 }
 
+bool B3IRGenerator::addCall(unsigned functionIndex, const FunctionInformation& info, Vector<ExpressionType>& args, ExpressionType& result)
+{
+    ASSERT(info.signature->arguments.size() == args.size());
+
+    Type returnType = info.signature->returnType;
+
+    size_t callIndex = m_unlinkedCalls.size();
+    m_unlinkedCalls.grow(callIndex + 1);
+    result = wasmCallingConvention().setupCall(m_proc, m_currentBlock, Origin(), args, toB3Type(returnType),
+        [&] (PatchpointValue* patchpoint) {
+            patchpoint->effects.writesPinned = true;
+            patchpoint->effects.readsPinned = true;
+
+            patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+                AllowMacroScratchRegisterUsage allowScratch(jit);
+
+                CCallHelpers::Call call = jit.call();
+
+                jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
+                    m_unlinkedCalls[callIndex] = { linkBuffer.locationOf(call), functionIndex };
+                });
+            });
+        });
+    return true;
+}
+
 bool B3IRGenerator::isContinuationReachable(ControlData& data)
 {
     // If nothing targets the continuation of the current block then we don't want to create
@@ -642,18 +673,27 @@ static std::unique_ptr<Compilation> createJSWrapper(VM& vm, const Signature* sig
 
     // Get our arguments.
     Vector<Value*> arguments;
-    jscCallingConvention().iterate(signature->arguments, proc, block, Origin(), [&] (Value* argument, unsigned) {
+    jscCallingConvention().loadArguments(signature->arguments, proc, block, Origin(), [&] (Value* argument, unsigned) {
         arguments.append(argument);
     });
 
     // Move the arguments into place.
-    Value* result = jscCallingConvention().setupCall(proc, block, Origin(), mainFunction, arguments, toB3Type(signature->returnType), [&] (PatchpointValue* patchpoint) {
+    Value* result = wasmCallingConvention().setupCall(proc, block, Origin(), arguments, toB3Type(signature->returnType), [&] (PatchpointValue* patchpoint) {
         if (memory) {
             ASSERT(sizes.size() == memory->pinnedRegisters().sizeRegisters.size());
             patchpoint->append(ConstrainedValue(baseMemory, ValueRep::reg(memory->pinnedRegisters().baseMemoryPointer)));
             for (unsigned i = 0; i < sizes.size(); ++i)
                 patchpoint->append(ConstrainedValue(sizes[i], ValueRep::reg(memory->pinnedRegisters().sizeRegisters[i].sizeRegister)));
         }
+
+        patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+            AllowMacroScratchRegisterUsage allowScratch(jit);
+
+            CCallHelpers::Call call = jit.call();
+            jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
+                linkBuffer.link(call, FunctionPtr(mainFunction.executableAddress()));
+            });
+        });
     });
 
     // Return the result, if needed.
@@ -665,11 +705,13 @@ static std::unique_ptr<Compilation> createJSWrapper(VM& vm, const Signature* sig
     return std::make_unique<Compilation>(vm, proc);
 }
 
-std::unique_ptr<FunctionCompilation> parseAndCompile(VM& vm, Vector<uint8_t>& source, Memory* memory, FunctionInformation info, unsigned optLevel)
+std::unique_ptr<FunctionCompilation> parseAndCompile(VM& vm, Vector<uint8_t>& source, Memory* memory, FunctionInformation info, const Vector<FunctionInformation>& functions, unsigned optLevel)
 {
+    auto result = std::make_unique<FunctionCompilation>();
+
     Procedure procedure;
-    B3IRGenerator context(memory, procedure);
-    FunctionParser<B3IRGenerator> parser(context, source, info);
+    B3IRGenerator context(memory, procedure, result->unlinkedCalls);
+    FunctionParser<B3IRGenerator> parser(context, source, info, functions);
     if (!parser.parse())
         RELEASE_ASSERT_NOT_REACHED();
 
@@ -679,7 +721,6 @@ std::unique_ptr<FunctionCompilation> parseAndCompile(VM& vm, Vector<uint8_t>& so
     fixSSA(procedure);
     if (verbose)
         dataLog("Post SSA: ", procedure);
-    auto result = std::make_unique<FunctionCompilation>();
 
     result->code = std::make_unique<Compilation>(vm, procedure, optLevel);
     result->jsEntryPoint = createJSWrapper(vm, info.signature, result->code->code(), memory);
index f8282d0..dfbc83f 100644 (file)
@@ -37,7 +37,7 @@ namespace JSC { namespace WASM {
 
 class Memory;
 
-std::unique_ptr<FunctionCompilation> parseAndCompile(VM&, Vector<uint8_t>&, Memory*, FunctionInformation, unsigned optLevel = 1);
+std::unique_ptr<FunctionCompilation> parseAndCompile(VM&, Vector<uint8_t>&, Memory*, FunctionInformation, const Vector<FunctionInformation>&, unsigned optLevel = 1);
 
 } } // namespace JSC::WASM
 
index 914aecc..7acbbc2 100644 (file)
@@ -37,12 +37,31 @@ const JSCCallingConvention& jscCallingConvention()
     static LazyNeverDestroyed<JSCCallingConvention> staticJSCCallingConvention;
     static std::once_flag staticJSCCallingConventionFlag;
     std::call_once(staticJSCCallingConventionFlag, [] () {
-        staticJSCCallingConvention.construct(Vector<GPRReg>(), RegisterSet::calleeSaveRegisters());
+        staticJSCCallingConvention.construct(Vector<Reg>(), Vector<Reg>(), RegisterSet::calleeSaveRegisters());
     });
 
     return staticJSCCallingConvention;
 }
 
+const WASMCallingConvention& wasmCallingConvention()
+{
+    static LazyNeverDestroyed<JSCCallingConvention> staticWASMCallingConvention;
+    static std::once_flag staticWASMCallingConventionFlag;
+    std::call_once(staticWASMCallingConventionFlag, [] () {
+        Vector<Reg> gprArgumentRegisters(GPRInfo::numberOfArgumentRegisters);
+        for (unsigned i = 0; i < GPRInfo::numberOfArgumentRegisters; ++i)
+            gprArgumentRegisters[i] = GPRInfo::toArgumentRegister(i);
+
+        Vector<Reg> fprArgumentRegisters(FPRInfo::numberOfArgumentRegisters);
+        for (unsigned i = 0; i < FPRInfo::numberOfArgumentRegisters; ++i)
+            fprArgumentRegisters[i] = FPRInfo::toArgumentRegister(i);
+
+        staticWASMCallingConvention.construct(WTFMove(gprArgumentRegisters), WTFMove(fprArgumentRegisters), RegisterSet::calleeSaveRegisters());
+    });
+
+    return staticWASMCallingConvention;
+}
+
 } } // namespace JSC::WASM
 
 #endif // ENABLE(B3_JIT)
index 6135f73..16eb548 100644 (file)
@@ -48,61 +48,91 @@ typedef unsigned (*NextOffset)(unsigned currentOffset, B3::Type type);
 template<unsigned headerSize, NextOffset updateOffset>
 class CallingConvention {
 public:
-    CallingConvention(Vector<GPRReg>&& registerArguments, RegisterSet&& calleeSaveRegisters)
-        : m_registerArguments(registerArguments)
+    CallingConvention(Vector<Reg>&& gprArgs, Vector<Reg>&& fprArgs, RegisterSet&& calleeSaveRegisters)
+        : m_gprArgs(gprArgs)
+        , m_fprArgs(fprArgs)
         , m_calleeSaveRegisters(calleeSaveRegisters)
     {
     }
 
+private:
+    B3::ValueRep marshallArgumentImpl(Vector<Reg> regArgs, B3::Type type, size_t& count, size_t& stackOffset) const
+    {
+        if (count < regArgs.size())
+            return B3::ValueRep::reg(regArgs[count++]);
+
+        count++;
+        B3::ValueRep result = B3::ValueRep::stackArgument(stackOffset);
+        stackOffset = updateOffset(stackOffset, type);
+        return result;
+    }
+
+    B3::ValueRep marshallArgument(B3::Type type, size_t& gpArgumentCount, size_t& fpArgumentCount, size_t& stackOffset) const
+    {
+        switch (type) {
+        case B3::Int32:
+        case B3::Int64:
+            return marshallArgumentImpl(m_gprArgs, type, gpArgumentCount, stackOffset);
+        case B3::Float:
+        case B3::Double:
+            return marshallArgumentImpl(m_fprArgs, type, fpArgumentCount, stackOffset);
+        case Void:
+            break;
+        }
+        RELEASE_ASSERT_NOT_REACHED();
+    }
+
+public:
     template<typename Functor>
-    void iterate(const Vector<Type>& argumentTypes, B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Functor& functor) const
+    void loadArguments(const Vector<Type>& argumentTypes, B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Functor& functor) const
     {
-        unsigned currentOffset = headerSize;
         B3::Value* framePointer = block->appendNew<B3::Value>(proc, B3::FramePointer, origin);
 
-        for (unsigned i = 0; i < argumentTypes.size(); ++i) {
+        size_t gpArgumentCount = 0;
+        size_t fpArgumentCount = 0;
+        size_t stackOffset = headerSize;
+
+        for (size_t i = 0; i < argumentTypes.size(); ++i) {
+            B3::Type type = toB3Type(argumentTypes[i]);
             B3::Value* argument;
-            if (i < m_registerArguments.size())
-                argument = block->appendNew<B3::ArgumentRegValue>(proc, origin, m_registerArguments[i]);
-            else {
+            B3::ValueRep rep = marshallArgument(type, gpArgumentCount, fpArgumentCount, stackOffset);
+            if (rep.isReg()) {
+                argument = block->appendNew<B3::ArgumentRegValue>(proc, origin, rep.reg());
+                if (type == B3::Int32)
+                    argument = block->appendNew<B3::Value>(proc, B3::Trunc, origin, argument);
+                // FIXME: How do I get a float from a FPR? We don't support floating points yet so it's not a big deal... yet.
+                // see: https://bugs.webkit.org/show_bug.cgi?id=163770
+            } else {
+                ASSERT(rep.isStackArgument());
                 B3::Value* address = block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer,
-                    block->appendNew<B3::Const64Value>(proc, origin, currentOffset));
-                argument = block->appendNew<B3::MemoryValue>(proc, B3::Load, toB3Type(argumentTypes[i]), origin, address);
-                currentOffset = updateOffset(currentOffset, toB3Type(argumentTypes[i]));
+                    block->appendNew<B3::Const64Value>(proc, origin, rep.offsetFromSP()));
+                argument = block->appendNew<B3::MemoryValue>(proc, B3::Load, type, origin, address);
             }
             functor(argument, i);
         }
     }
 
+    // It's expected that the pachpointFunctor sets the generator for the call operation.
     template<typename Functor>
-    B3::Value* setupCall(B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, MacroAssemblerCodePtr target, const Vector<B3::Value*>& arguments, B3::Type returnType, const Functor& patchpointFunctor) const
+    B3::Value* setupCall(B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Vector<B3::Value*>& arguments, B3::Type returnType, const Functor& patchpointFunctor) const
     {
-        size_t stackArgumentCount = arguments.size() < m_registerArguments.size() ? 0 : arguments.size() - m_registerArguments.size();
-        unsigned offset = headerSize - sizeof(CallerFrameAndPC);
+        size_t gpArgumentCount = 0;
+        size_t fpArgumentCount = 0;
+        size_t stackOffset = headerSize - sizeof(CallerFrameAndPC);
 
-        proc.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), headerSize + (stackArgumentCount * sizeof(Register))));
         Vector<B3::ConstrainedValue> constrainedArguments;
-        for (unsigned i = 0; i < arguments.size(); ++i) {
-            B3::ValueRep rep;
-            if (i < m_registerArguments.size())
-                rep = B3::ValueRep::reg(m_registerArguments[i]);
-            else
-                rep = B3::ValueRep::stackArgument(offset);
-            constrainedArguments.append(B3::ConstrainedValue(arguments[i], rep));
-            offset = updateOffset(offset, arguments[i]->type());
+        for (B3::Value* argument : arguments) {
+            B3::ValueRep rep = marshallArgument(argument->type(), gpArgumentCount, fpArgumentCount, stackOffset);
+            constrainedArguments.append(B3::ConstrainedValue(argument, rep));
         }
 
+        proc.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), stackOffset));
+
         B3::PatchpointValue* patchpoint = block->appendNew<B3::PatchpointValue>(proc, returnType, origin);
+        patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
+        patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
         patchpoint->appendVector(constrainedArguments);
         patchpointFunctor(patchpoint);
-        patchpoint->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
-            AllowMacroScratchRegisterUsage allowScratch(jit);
-
-            CCallHelpers::Call call = jit.call();
-            jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
-                linkBuffer.link(call, FunctionPtr(target.executableAddress()));
-            });
-        });
 
         if (returnType == B3::Void)
             return nullptr;
@@ -111,7 +141,8 @@ public:
         return patchpoint;
     }
 
-    const Vector<GPRReg> m_registerArguments;
+    const Vector<Reg> m_gprArgs;
+    const Vector<Reg> m_fprArgs;
     const RegisterSet m_calleeSaveRegisters;
     const RegisterSet m_callerSaveRegisters;
 };
@@ -124,7 +155,10 @@ inline unsigned nextJSCOffset(unsigned currentOffset, B3::Type)
 constexpr unsigned jscHeaderSize = ExecState::headerSizeInRegisters * sizeof(Register);
 typedef CallingConvention<jscHeaderSize, nextJSCOffset> JSCCallingConvention;
 
+typedef JSCCallingConvention WASMCallingConvention;
+
 const JSCCallingConvention& jscCallingConvention();
+const WASMCallingConvention& wasmCallingConvention();
 
 } } // namespace JSC::WASM
 
index 1e10810..ac46ff3 100644 (file)
@@ -46,6 +46,7 @@
 
 #include "B3Compilation.h"
 #include "B3Type.h"
+#include "CodeLocation.h"
 #include <wtf/Vector.h>
 #include <wtf/text/WTFString.h>
 
@@ -127,7 +128,13 @@ struct FunctionInformation {
     size_t end;
 };
 
+struct UnlinkedCall {
+    CodeLocationCall callLocation;
+    size_t functionIndex;
+};
+
 struct FunctionCompilation {
+    Vector<UnlinkedCall> unlinkedCalls;
     std::unique_ptr<B3::Compilation> code;
     std::unique_ptr<B3::Compilation> jsEntryPoint;
 };
index 4471a2c..452e28b 100644 (file)
@@ -44,7 +44,7 @@ public:
     typedef typename Context::ExpressionType ExpressionType;
     typedef typename Context::ControlType ControlType;
 
-    FunctionParser(Context&, const Vector<uint8_t>& sourceBuffer, const FunctionInformation&);
+    FunctionParser(Context&, const Vector<uint8_t>& sourceBuffer, const FunctionInformation&, const Vector<FunctionInformation>& functions);
 
     bool WARN_UNUSED_RETURN parse();
 
@@ -60,14 +60,16 @@ private:
     Vector<ExpressionType, 1> m_expressionStack;
     Vector<ControlType> m_controlStack;
     const Signature& m_signature;
+    const Vector<FunctionInformation>& m_functions;
     unsigned m_unreachableBlocks { 0 };
 };
 
 template<typename Context>
-FunctionParser<Context>::FunctionParser(Context& context, const Vector<uint8_t>& sourceBuffer, const FunctionInformation& info)
+FunctionParser<Context>::FunctionParser(Context& context, const Vector<uint8_t>& sourceBuffer, const FunctionInformation& info, const Vector<FunctionInformation>& functions)
     : Parser(sourceBuffer, info.start, info.end)
     , m_context(context)
     , m_signature(*info.signature)
+    , m_functions(functions)
 {
     if (verbose)
         dataLogLn("Parsing function starting at: ", info.start, " ending at: ", info.end);
@@ -101,8 +103,11 @@ bool FunctionParser<Context>::parseBlock()
 {
     while (true) {
         uint8_t op;
-        if (!parseUInt7(op) || !isValidOpType(op))
+        if (!parseUInt7(op) || !isValidOpType(op)) {
+            if (verbose)
+                WTF::dataLogLn("attempted to decode invalid op: ", RawPointer(reinterpret_cast<void*>(op)), " at offset: ", RawPointer(reinterpret_cast<void*>(m_offset)));
             return false;
+        }
 
         if (verbose) {
             dataLogLn("processing op (", m_unreachableBlocks, "): ",  RawPointer(reinterpret_cast<void*>(op)), " at offset: ", RawPointer(reinterpret_cast<void*>(m_offset)));
@@ -193,6 +198,14 @@ bool FunctionParser<Context>::parseExpression(OpType op)
         return true;
     }
 
+    case OpType::I64Const: {
+        uint64_t constant;
+        if (!parseVarUInt64(constant))
+            return false;
+        m_expressionStack.append(m_context.addConstant(I64, constant));
+        return true;
+    }
+
     case OpType::GetLocal: {
         uint32_t index;
         if (!parseVarUInt32(index))
@@ -213,6 +226,30 @@ bool FunctionParser<Context>::parseExpression(OpType op)
         return m_context.setLocal(index, value);
     }
 
+    case OpType::Call: {
+        uint32_t functionIndex;
+        if (!parseVarUInt32(functionIndex))
+            return false;
+
+        if (functionIndex >= m_functions.size())
+            return false;
+
+        const FunctionInformation& info = m_functions[functionIndex];
+
+        Vector<ExpressionType> args;
+        for (unsigned i = 0; i < info.signature->arguments.size(); ++i)
+            args.append(m_expressionStack.takeLast());
+
+        ExpressionType result = Context::emptyExpression;
+        if (!m_context.addCall(functionIndex, info, args, result))
+            return false;
+
+        if (result != Context::emptyExpression)
+            m_expressionStack.append(result);
+
+        return true;
+    }
+
     case OpType::Block: {
         Type inlineSignature;
         if (!parseValueType(inlineSignature))
@@ -281,12 +318,12 @@ bool FunctionParser<Context>::parseExpression(OpType op)
     case OpType::BrTable:
     case OpType::Nop:
     case OpType::Drop:
-    case OpType::I64Const:
     case OpType::F32Const:
     case OpType::F64Const:
     case OpType::TeeLocal:
     case OpType::GetGlobal:
     case OpType::SetGlobal:
+    case OpType::CallIndirect:
         // FIXME: Not yet implemented.
         return false;
     }
index dcde74c..0e796bf 100644 (file)
@@ -82,7 +82,7 @@ bool ModuleParser::parse()
                 return false;
 
             // Make sure we can read up to the section's size.
-            if (m_offset + sectionNameLength + maxLEBByteLength >= m_sourceLength)
+            if (m_offset + sectionNameLength + WTF::LEBDecoder::max32BitLEBByteLength >= m_sourceLength)
                 return false;
 
             // We don't support any custom sections yet.
index 6d3488c..66f7289 100644 (file)
@@ -42,7 +42,9 @@ namespace JSC { namespace WASM {
     macro(SetLocal, 0x15, Oops) \
     macro(TeeLocal, 0x19, Oops) \
     macro(GetGlobal, 0xbb, Oops) \
-    macro(SetGlobal, 0xbc, Oops)
+    macro(SetGlobal, 0xbc, Oops) \
+    macro(Call, 0x16, Oops) \
+    macro(CallIndirect, 0x17, Oops)
 
 #define FOR_EACH_WASM_CONTROL_FLOW_OP(macro) \
     macro(Unreachable, 0x0, Oops) \
index 5369b6f..1b79186 100644 (file)
@@ -46,7 +46,8 @@ protected:
     bool WARN_UNUSED_RETURN parseVarUInt1(uint8_t& result);
     bool WARN_UNUSED_RETURN parseUInt7(uint8_t& result);
     bool WARN_UNUSED_RETURN parseUInt32(uint32_t& result);
-    bool WARN_UNUSED_RETURN parseVarUInt32(uint32_t& result) { return decodeUInt32(m_source.data(), m_sourceLength, m_offset, result); }
+    bool WARN_UNUSED_RETURN parseVarUInt32(uint32_t& result) { return WTF::LEBDecoder::decodeUInt32(m_source.data(), m_sourceLength, m_offset, result); }
+    bool WARN_UNUSED_RETURN parseVarUInt64(uint64_t& result) { return WTF::LEBDecoder::decodeUInt64(m_source.data(), m_sourceLength, m_offset, result); }
 
 
     bool WARN_UNUSED_RETURN parseValueType(Type& result);
index a083738..ba298ff 100644 (file)
@@ -54,8 +54,16 @@ Plan::Plan(VM& vm, Vector<uint8_t> source)
     for (const FunctionInformation& info : moduleParser.functionInformation()) {
         if (verbose)
             dataLogLn("Processing funcion starting at: ", info.start, " and ending at: ", info.end);
-        result.append(parseAndCompile(vm, source, moduleParser.memory().get(), info));
+        result.append(parseAndCompile(vm, source, moduleParser.memory().get(), info, moduleParser.functionInformation()));
     }
+
+    // Patch the call sites for each function.
+    for (std::unique_ptr<FunctionCompilation>& functionPtr : result) {
+        FunctionCompilation* function = functionPtr.get();
+        for (auto& call : function->unlinkedCalls)
+            MacroAssembler::repatchCall(call.callLocation, CodeLocationLabel(result[call.functionIndex]->code->code()));
+    }
+
     memory = WTFMove(moduleParser.memory());
 }
 
index fb77570..779ae4c 100644 (file)
@@ -1,3 +1,20 @@
+2016-10-20  Keith Miller  <keith_miller@apple.com>
+
+        Add support for WASM calls
+        https://bugs.webkit.org/show_bug.cgi?id=161727
+
+        Reviewed by Filip Pizlo and Michael Saboff.
+
+        Added a new decodeUInt64. Also, added WTF::LEBDecoder namespace.
+
+        * wtf/LEBDecoder.h:
+        (WTF::LEBDecoder::decodeUInt):
+        (WTF::LEBDecoder::decodeUInt32):
+        (WTF::LEBDecoder::decodeUInt64):
+        (WTF::LEBDecoder::decodeInt32):
+        (decodeUInt32): Deleted.
+        (decodeInt32): Deleted.
+
 2016-10-20  Filip Pizlo  <fpizlo@apple.com>
 
         The tracking of the coarse-grain Heap state (allocating or not, collector or not, eden vs full) should respect the orthogonality between allocating and collecting
index 36dcca2..1650a23 100644 (file)
 // See https://en.wikipedia.org/wiki/LEB128 for more information about the
 // LEB format.
 
-const size_t maxLEBByteLength = 5;
+namespace WTF { namespace LEBDecoder {
 
-inline bool WARN_UNUSED_RETURN decodeUInt32(const uint8_t* bytes, size_t length, size_t& offset, uint32_t& result)
+template<size_t maxByteLength, typename T>
+inline bool WARN_UNUSED_RETURN decodeUInt(const uint8_t* bytes, size_t length, size_t& offset, T& result)
 {
     ASSERT(length > offset);
     result = 0;
     unsigned shift = 0;
-    size_t last = std::min(maxLEBByteLength, length - offset - 1);
+    size_t last = std::min(maxByteLength, length - offset - 1);
     for (unsigned i = 0; true; ++i) {
         uint8_t byte = bytes[offset++];
         result |= (byte & 0x7f) << shift;
@@ -53,12 +54,25 @@ inline bool WARN_UNUSED_RETURN decodeUInt32(const uint8_t* bytes, size_t length,
     return true;
 }
 
+const size_t max32BitLEBByteLength = 5;
+const size_t max64BitLEBByteLength = 10;
+
+inline bool WARN_UNUSED_RETURN decodeUInt32(const uint8_t* bytes, size_t length, size_t& offset, uint32_t& result)
+{
+    return decodeUInt<max32BitLEBByteLength, uint32_t>(bytes, length, offset, result);
+}
+
+inline bool WARN_UNUSED_RETURN decodeUInt64(const uint8_t* bytes, size_t length, size_t& offset, uint64_t& result)
+{
+    return decodeUInt<max64BitLEBByteLength, uint64_t>(bytes, length, offset, result);
+}
+
 inline bool WARN_UNUSED_RETURN decodeInt32(const uint8_t* bytes, size_t length, size_t& offset, int32_t& result)
 {
     ASSERT(length > offset);
     result = 0;
     unsigned shift = 0;
-    size_t last = std::min(maxLEBByteLength, length - offset - 1);
+    size_t last = std::min(max32BitLEBByteLength, length - offset - 1);
     uint8_t byte;
     for (unsigned i = 0; true; ++i) {
         byte = bytes[offset++];
@@ -74,3 +88,5 @@ inline bool WARN_UNUSED_RETURN decodeInt32(const uint8_t* bytes, size_t length,
         result |= ((-1u) << shift);
     return true;
 }
+
+} } // WTF::LEBDecoder