Website Summary Files Commits Branches @commit @diff @stats
path: root / website / content / slides / 06_vm_values / _index.md
blob: bb236fa57764ffed8743f504646d3fea9982fba0 (plain) (blame)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
---
title: "Values in the VM"
date: 2018-11-21T17:45:53+03:00
draft: false
outputs: ["Reveal"]
---

# Values in the VM

- Languages and VMs
- NanBoxing
- NunBoxing
- Pointer compression
- Tagged arithmetic

---

# Language Semantics and VM

The VM implementation depends a lot on language semantics

- typing - static or dynamic?
- number types - integers and floating point
- strings - immutability

---

That is why it is almost every language has its own VM.

---

# Values in the VM?

    function add(a, b) {
        return a + b
    }


---

    add r1 a1 a2
    ret r1

---

- must work for numbers
- must work for strings
- must work for strings and numbers mixed
- can work for objects and arrays
    - if the target language has semantics for that

---

## How do we support that?

C++ is statically type, the value in the registers can have a single C++ type

---

## Axiom of CS

> *Every* problem can be solved by adding another level of indirection.

> ..., except too many indirections.

---

### Polymorhism

- C++ way for type erasure

- Base class with virtual methods

---

    struct Value {
        virtual ValuePtr Add(ValuePtr rhs) const = 0;
        // ...
    }

    struct NumberValue : Value {
        double m_Number;
    }

---

#### Performance

- `sizeof(NumberValue)` - 16 B - 100% overhead
    - pointer + double
- cache miss on every access to the number
- virtual call for adding two numbers

- It is so inefficient, that almost nobody does it this way

---
### What we can do?

---

    struct Value {
        bool m_IsDouble;
        union {
            double as_Double;
            void* as_Pointer;
        } m_Value;
    }

---

- still 100% memory overhead because m_Value will be aligned to 8 bytes
    - will not fit in a register
    - more cache misses

---
### Doubles and IEEE 

A double has 64-bits, from least to most significant:

1. 52 bits - mantissa
    - a leading 1 bit set to 1 is assumed, so the precision is 53 bits
2. 11 bits - exponent - from 0 to 2047
3. 1 bit - sign

Value = Mantissa * 2 ^ (exponent - 1023)

---

Not all possible values are actually valid numbers.

- Infinity - exponent is 2047, mantissa - 0


---

    double r = sqrt(-1);

 NaN - Not a Number

- sign bit is set
- exponent is all 1 (2047)
- most significant bit of the mantissa is 1
- the rest 51 bits do not matter - it is always a NaN
    - the CPU always sets them to 0


---
# The rest 51 bits do not matter

- so we can (ab)use them to store our pointers!
- but one pointer is 64 bits?

---

> For x86_64 / arm64 the addresses above 2^48 are reserved and can't be used
> from user applications

---

- 48 bits for the pointer
- 3 bits for ... type

---
### Type

    enum ValueType {
        Number = 0,
        Null = 1,
        Undefined = 2,
        Boolean = 3,
        String = 4,
        Array = 5,
        Object = 6,
        Function= 7,
    };

---
#### Why Number is 0?

- so that we treat regular NaNs as numbers.

---
#### NanBoxing


Need to split easily `double` to bits - use union to share the memory between a
`double` and structs with bitfields.

    union {
        double as_double;
        NanPointer as_pointer;
        CheckType to_check;

    } m_value;

---

    struct NanPointer
    {
        // order from least to most significant bits
        // bit field - has type size_t, but only 48 bits
        size_t pointer:48;
        size_t tag:3; // 0 for a real nan and non zero for out type
        size_t nan:13; // must be all 1s to force a nan
    };

---

    // Used to check whether the value is a double or not
    struct CheckType
    {
        size_t payload: 48;
        size_t check:16;
    };

---

    bool is_double() const
    {
        // If any of the least significant bits is non-zero,
        // then this is not a normal double or a NaN value
        return m_value.to_check.check <= 0xFFF8;
    }

    double get_double() const
    {
        assert(is_double());
        return m_value.as_double;
    }

---

    int get_tag() {
        assert(!is_double());
        return m_value.as_pointer.tag;
    }

    void* get_pointer() const {
        return (void*)(size_t) m_value.as_pointer.pointer;
    }

---

#?

---

## Strings

Strings in JavaScript are immutable, which means that changing one string
returns another.


---

#### String interning

> A method of storing only one instance of any given string, which is immutable.

This means that there is only one copy of `"answer"` inside the VM

---

Comparing strings for equality becomes very fast.

> Two strings are the same if and only if their pointers are the same.

---

    struct StringValue
    {
        std::string Value;
        // extra data
        // int RefCount;
        // int GCFlags;

    }

---
    class Spasm
    {
        typedef std::unordered_set<StringValue> StringTable;

        StringTable m_Strings;
    };

---

    Value Spasm::AllocateString(const char* s) {
        auto current = m_Strings.find(s);
        if (current != m_Strings.end())
        {
            return Value(ValueType::String, &(*current));
        }
        auto insert = m_Strings.insert(StringValue(s));
        return Value(ValueType::String, &(*insert.second));
    }


---

# ?