1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#ifndef ECMASCRIPT_SHARED_OBJECTS_CONCURRENT_API_SCOPE_H
17#define ECMASCRIPT_SHARED_OBJECTS_CONCURRENT_API_SCOPE_H
18
19#include "ecmascript/js_object.h"
20#include "ecmascript/shared_objects/js_shared_set.h"
21#include "ecmascript/shared_objects/js_shared_typed_array.h"
22
23#include "ecmascript/containers/containers_errors.h"
24#include "macros.h"
25
26namespace panda::ecmascript {
27enum class ModType : uint8_t {
28    READ = 0,
29    WRITE = 1
30};
31template<typename Container, ModType modType = ModType::READ>
32class ConcurrentApiScope final {
33public:
34    ConcurrentApiScope(JSThread *thread, const JSHandle<JSTaggedValue> &objHandle, SCheckMode mode = SCheckMode::CHECK)
35        : thread_(thread), objHandle_(objHandle), checkMode_(mode)
36    {
37        if (checkMode_ == SCheckMode::SKIP) {
38            return;
39        }
40        if constexpr (modType == ModType::READ) {
41            CanRead();
42        } else {
43            CanWrite();
44        }
45    }
46
47    ~ConcurrentApiScope()
48    {
49        if (checkMode_ == SCheckMode::SKIP) {
50            return;
51        }
52        if constexpr (modType == ModType::READ) {
53            ReadDone();
54        } else {
55            WriteDone();
56        }
57    }
58
59    static constexpr uint32_t WRITE_MOD_MASK = 1 << 31;
60
61private:
62    NO_COPY_SEMANTIC(ConcurrentApiScope);
63    NO_MOVE_SEMANTIC(ConcurrentApiScope);
64    inline uint32_t GetModRecord()
65    {
66        return reinterpret_cast<volatile std::atomic<uint32_t> *>(
67            ToUintPtr(objHandle_->GetTaggedObject()) +
68            Container::MOD_RECORD_OFFSET)->load(std::memory_order_acquire);
69    }
70
71    inline void CanWrite()
72    {
73        // Set to ModType::WRITE, expect no writer and readers
74        constexpr uint32_t expectedModRecord = 0;
75        constexpr uint32_t desiredModRecord = WRITE_MOD_MASK;
76        uint32_t ret = Barriers::AtomicSetPrimitive(objHandle_->GetTaggedObject(),
77            Container::MOD_RECORD_OFFSET, expectedModRecord, desiredModRecord);
78        if (ret != expectedModRecord) {
79            auto error = containers::ContainerError::BusinessError(
80                thread_, containers::ErrorFlag::CONCURRENT_MODIFICATION_ERROR, "Concurrent modification exception");
81            THROW_NEW_ERROR_AND_RETURN(thread_, error);
82        }
83    }
84
85    inline void WriteDone()
86    {
87        constexpr uint32_t expectedModRecord = WRITE_MOD_MASK;
88        constexpr uint32_t desiredModRecord = 0u;
89        uint32_t ret = Barriers::AtomicSetPrimitive(objHandle_->GetTaggedObject(),
90            Container::MOD_RECORD_OFFSET, expectedModRecord, desiredModRecord);
91        if (ret != expectedModRecord) {
92            auto error = containers::ContainerError::BusinessError(
93                thread_, containers::ErrorFlag::CONCURRENT_MODIFICATION_ERROR, "Concurrent modification exception");
94            THROW_NEW_ERROR_AND_RETURN(thread_, error);
95        }
96    }
97
98    inline void CanRead()
99    {
100        while (true) {
101            // Expect no writers
102            expectModRecord_ = GetModRecord();
103            if ((expectModRecord_ & WRITE_MOD_MASK)) {
104                auto error = containers::ContainerError::BusinessError(
105                    thread_, containers::ErrorFlag::CONCURRENT_MODIFICATION_ERROR, "Concurrent modification exception");
106                THROW_NEW_ERROR_AND_RETURN(thread_, error);
107            }
108            // Increase readers by 1
109            desiredModRecord_ = expectModRecord_ + 1;
110            auto ret = Barriers::AtomicSetPrimitive(objHandle_->GetTaggedObject(),
111                Container::MOD_RECORD_OFFSET, expectModRecord_, desiredModRecord_);
112            if (ret == expectModRecord_) {
113                break;
114            }
115        }
116    }
117
118    inline void ReadDone()
119    {
120        std::swap(expectModRecord_, desiredModRecord_);
121        while (true) {
122            auto ret = Barriers::AtomicSetPrimitive(objHandle_->GetTaggedObject(),
123                Container::MOD_RECORD_OFFSET, expectModRecord_, desiredModRecord_);
124            if (ret == expectModRecord_) {
125                break;
126            }
127            expectModRecord_ = GetModRecord();
128            if ((expectModRecord_ & WRITE_MOD_MASK) ||
129                 expectModRecord_ == 0) {
130                auto error = containers::ContainerError::BusinessError(
131                    thread_, containers::ErrorFlag::CONCURRENT_MODIFICATION_ERROR, "Concurrent modification exception");
132                THROW_NEW_ERROR_AND_RETURN(thread_, error);
133            }
134            // Decrease readers by 1
135            desiredModRecord_ = expectModRecord_ - 1;
136        }
137    }
138
139    JSThread *thread_ {nullptr};
140    JSHandle<JSTaggedValue> objHandle_;
141    SCheckMode checkMode_ { SCheckMode::CHECK };
142    // For readers
143    uint32_t expectModRecord_ {0};
144    uint32_t desiredModRecord_ {0};
145
146    static_assert(std::is_same_v<Container, JSSharedSet> || std::is_same_v<Container, JSSharedMap> ||
147                  std::is_same_v<Container, JSSharedArray> || std::is_same_v<Container, JSSharedTypedArray> ||
148                  std::is_same_v<Container, JSAPIBitVector>);
149};
150} // namespace panda::ecmascript
151#endif  // ECMASCRIPT_SHARED_OBJECTS_CONCURRENT_API_SCOPE_H