summaryrefslogtreecommitdiffstats
path: root/js/src/vm/InlineCharBuffer-inl.h
blob: ac006a0d986039f7ac49488d2bf564f61fe18d5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_InlineCharBuffer_inl_h
#define vm_InlineCharBuffer_inl_h

#include "vm/StringType-inl.h"

namespace js {

template <typename CharT>
struct MaximumInlineLength;

template <>
struct MaximumInlineLength<Latin1Char> {
  static constexpr size_t value = JSFatInlineString::MAX_LENGTH_LATIN1;
};

template <>
struct MaximumInlineLength<char16_t> {
  static constexpr size_t value = JSFatInlineString::MAX_LENGTH_TWO_BYTE;
};

// Character buffer class used for ToLowerCase and ToUpperCase operations, as
// well as other string operations where the final string length is known in
// advance.
//
// Case conversion operations normally return a string with the same length as
// the input string. To avoid over-allocation, we optimistically allocate an
// array with same size as the input string and only when we detect special
// casing characters, which can change the output string length, we reallocate
// the output buffer to the final string length.
//
// As a further mean to improve runtime performance, the character buffer
// contains an inline storage, so we don't need to heap-allocate an array when
// a JSInlineString will be used for the output string.
//
// Why not use mozilla::Vector instead? mozilla::Vector doesn't provide enough
// fine-grained control to avoid over-allocation when (re)allocating for exact
// buffer sizes. This led to visible performance regressions in µ-benchmarks.
template <typename CharT>
class MOZ_NON_PARAM InlineCharBuffer {
  static constexpr size_t InlineCapacity = MaximumInlineLength<CharT>::value;

  CharT inlineStorage[InlineCapacity];
  UniquePtr<CharT[], JS::FreePolicy> heapStorage;

#ifdef DEBUG
  // In debug mode, we keep track of the requested string lengths to ensure
  // all character buffer methods are called in the correct order and with
  // the expected argument values.
  size_t lastRequestedLength = 0;

  void assertValidRequest(size_t expectedLastLength, size_t length) {
    MOZ_ASSERT(length >= expectedLastLength, "cannot shrink requested length");
    MOZ_ASSERT(lastRequestedLength == expectedLastLength);
    lastRequestedLength = length;
  }
#else
  void assertValidRequest(size_t expectedLastLength, size_t length) {}
#endif

 public:
  CharT* get() { return heapStorage ? heapStorage.get() : inlineStorage; }

  bool maybeAlloc(JSContext* cx, size_t length) {
    assertValidRequest(0, length);

    if (length <= InlineCapacity) {
      return true;
    }

    MOZ_ASSERT(!heapStorage, "heap storage already allocated");
    heapStorage =
        cx->make_pod_arena_array<CharT>(js::StringBufferArena, length);
    return !!heapStorage;
  }

  bool maybeRealloc(JSContext* cx, size_t oldLength, size_t newLength) {
    assertValidRequest(oldLength, newLength);

    if (newLength <= InlineCapacity) {
      return true;
    }

    if (!heapStorage) {
      heapStorage =
          cx->make_pod_arena_array<CharT>(js::StringBufferArena, newLength);
      if (!heapStorage) {
        return false;
      }

      MOZ_ASSERT(oldLength <= InlineCapacity);
      mozilla::PodCopy(heapStorage.get(), inlineStorage, oldLength);
      return true;
    }

    CharT* oldChars = heapStorage.release();
    CharT* newChars = cx->pod_arena_realloc(js::StringBufferArena, oldChars,
                                            oldLength, newLength);
    if (!newChars) {
      js_free(oldChars);
      return false;
    }

    heapStorage.reset(newChars);
    return true;
  }

  JSString* toStringDontDeflate(JSContext* cx, size_t length,
                                js::gc::Heap heap = js::gc::Heap::Default) {
    MOZ_ASSERT(length == lastRequestedLength);

    if (JSInlineString::lengthFits<CharT>(length)) {
      MOZ_ASSERT(
          !heapStorage,
          "expected only inline storage when length fits in inline string");

      if (JSString* str = TryEmptyOrStaticString(cx, inlineStorage, length)) {
        return str;
      }

      mozilla::Range<const CharT> range(inlineStorage, length);
      return NewInlineString<CanGC>(cx, range, heap);
    }

    MOZ_ASSERT(heapStorage,
               "heap storage was not allocated for non-inline string");

    return NewStringDontDeflate<CanGC>(cx, std::move(heapStorage), length,
                                       heap);
  }

  JSString* toString(JSContext* cx, size_t length,
                     js::gc::Heap heap = js::gc::Heap::Default) {
    MOZ_ASSERT(length == lastRequestedLength);

    if (JSInlineString::lengthFits<CharT>(length)) {
      MOZ_ASSERT(
          !heapStorage,
          "expected only inline storage when length fits in inline string");

      return NewStringCopyN<CanGC>(cx, inlineStorage, length, heap);
    }

    MOZ_ASSERT(heapStorage,
               "heap storage was not allocated for non-inline string");

    return NewString<CanGC>(cx, std::move(heapStorage), length, heap);
  }
};

} /* namespace js */

#endif /* vm_InlineCharBuffer_inl_h */