Skip to content

Commit 7205cea

Browse files
committed
Excise unsafe blocks
1 parent 1692fed commit 7205cea

File tree

2 files changed

+20
-872
lines changed

2 files changed

+20
-872
lines changed

README.md

Lines changed: 18 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ This proposal introduces four (4) logical features:
1313
- [**Structs**](#structs), or unshared structs, which are fixed-layout objects. They behave like `class` instances, with more restrictions that are beneficial for optimizations and analysis.
1414
- [**Shared Structs**](#shared-structs), which are further restricted structs that can be shared and accessed in parallel by multiple agents. They enable shared memory multithreading.
1515
- [**Mutex and Condition**](#mutex-and-condition), which are higher level abstractions for synchronizing access to shared memory.
16-
- [**Unsafe Blocks**](#unsafe-blocks), which are syntactic guardrails to lexically scope where racy accesses to shared structs are allowed.
1716

1817
This proposal is intended to be minimal, but still useful by itself, without follow-up proposals.
1918

@@ -99,11 +98,9 @@ shared struct SharedBox {
9998
let sharedBox = new SharedBox();
10099
let sharedBox2 = new SharedBox();
101100

102-
unsafe {
103-
sharedBox.x = 42; // x is declared and rhs is primitive
104-
sharedBox.x = sharedBox2; // x is declared and rhs is shared
105-
assertThrows(() => { sharedBox.x = {}; }) // rhs is not a shared struct
106-
}
101+
sharedBox.x = 42; // x is declared and rhs is primitive
102+
sharedBox.x = sharedBox2; // x is declared and rhs is shared
103+
assertThrows(() => { sharedBox.x = {}; }) // rhs is not a shared struct
107104

108105
// can programmatically test if a value can be shared
109106
assert(Reflect.canBeShared(sharedBox2));
@@ -112,20 +109,16 @@ assert(!Reflect.canBeShared({}));
112109
let worker = new Worker('worker.js');
113110
worker.postMessage({ sharedBox });
114111

115-
unsafe {
116-
sharedBox.x = "main"; // x is declared and rhs is primitive
117-
console.log(sharedBox.x);
118-
}
112+
sharedBox.x = "main"; // x is declared and rhs is primitive
113+
console.log(sharedBox.x);
119114
```
120115

121116
```javascript
122117
// worker.js
123118
onmessage = function(e) {
124119
let sharedBox = e.data.sharedBox;
125-
unsafe {
126-
sharedBox.x = "worker"; // x is declared and rhs is primitive
127-
console.log(sharedBox.x);
128-
}
120+
sharedBox.x = "worker"; // x is declared and rhs is primitive
121+
console.log(sharedBox.x);
129122
};
130123
```
131124

@@ -141,31 +134,25 @@ Shared Arrays are a fixed-length arrays that may be shared across agents. They a
141134

142135
```javascript
143136
let sharedArray = new SharedArray(100);
144-
unsafe {
145-
assert(sharedArray.length === 100);
146-
for (i = 0; i < sharedArray.length; i++) {
147-
// like shared structs, all elements are initialized to undefined
148-
assert(sharedArray[i] === undefined);
149-
}
137+
assert(sharedArray.length === 100);
138+
for (i = 0; i < sharedArray.length; i++) {
139+
// like shared structs, all elements are initialized to undefined
140+
assert(sharedArray[i] === undefined);
150141
}
151142

152143
let worker = new Worker('worker_array.js');
153144
worker.postMessage({ sharedArray });
154145

155-
unsafe {
156-
sharedArray[0] = "main";
157-
console.log(sharedArray[0]);
158-
}
146+
sharedArray[0] = "main";
147+
console.log(sharedArray[0]);
159148
```
160149

161150
```javascript
162151
// worker_array.js
163152
onmessage = function(e) {
164153
let sharedArray = e.data.sharedArray;
165-
unsafe {
166-
sharedArray[0] = "worker";
167-
console.log(sharedArray[0]);
168-
}
154+
sharedArray[0] = "worker";
155+
console.log(sharedArray[0]);
169156
};
170157
```
171158

@@ -293,13 +280,13 @@ let worker = new Worker('worker_mutex.js');
293280
worker.postMessage({ point });
294281

295282
// assume this agent can block
296-
unsafe {
283+
{
297284
using lock = Atomics.Mutex.lock(point.mutex);
298285
point.x = "main";
299286
point.y = "main";
300287
}
301288

302-
unsafe {
289+
{
303290
using lock = Atomics.Mutex.lock(point.mutex);
304291
console.log(point.x, point.y);
305292
}
@@ -309,7 +296,7 @@ unsafe {
309296
// worker_mutex.js
310297
onmessage = function(e) {
311298
let point = e.data.point;
312-
unsafe {
299+
{
313300
using lock = Atomics.Mutex.lock(point.mutex);
314301
point.x = "worker";
315302
point.y = "worker";
@@ -374,58 +361,6 @@ shared struct Atomics.Condition {
374361
}
375362
```
376363
377-
### Unsafe Blocks
378-
379-
Correct multithreaded programs are difficult to write, because races are subtle and difficult to reason about. To decrease the likelihood of incorrect programs, accesses to shared structs are only allowed when lexically contained with in an `unsafe { }` block. Note that SharedArrayBuffer access remains allowed in all contexts for backwards compatibilty.
380-
381-
The `unsafe` keyword is a clear signal of intent that a developer is choosing to work with shared memory multithreaded code. The presence of an `unsafe` block is an indication to code reviewers that special care must be taken during review. It also is acts as a syntactic marker that future tooling (linters, type checkers, etc.) could use to identify data races.
382-
383-
An `unsafe {}` block is otherwise treated the same as a normal Block. Its only distinction is that it explicitly labels code within the block as potentially containing non-thread-safe (e.g., "unsafe") code. The general expectation is that any thread safety concerns should be addressed by the developer as control flow exits the unsafe block. For example, you could utilize using to synchronize access to a shared struct via a lock:
384-
385-
```javascript
386-
shared struct Counter {
387-
value = 0;
388-
}
389-
390-
// normal JS code, outside of an "unsafe" context
391-
const ctr = new Counter(); // allocations allowed
392-
assertThrows(() => ctr.value = 1); // error (writes shared memory)
393-
assertThrows(() => ctr.value); // error (reads shared memory)
394-
395-
// "unsafe" JS code
396-
unsafe {
397-
ctr.value = 1; // ok
398-
ctr.value; // ok
399-
}
400-
401-
function incrementCounter(ctr, mutex) {
402-
unsafe {
403-
using lck = Atomics.Mutex.lock(mutex);
404-
ctr.value++;
405-
}
406-
}
407-
```
408-
409-
Here, when the control enters the `unsafe` block, we allocate a lock against the provided mutex via a `using` declaration. As control exits the `unsafe` block, the lock tracked by using is released.
410-
411-
#### Lexically Scoped
412-
413-
The `unsafe` keyword is a syntactic marker that applies to lexically scoped reads and writes of the fields of a shared struct instance. Within an `unsafe` block, any lexically scoped accesses are permitted, even if they are nested within another function declared in the same block. This special lexical context shares some surface level similarities with the lexical scoping rules for private names, or the runtime semantics of "use strict".
414-
415-
Since `unsafe` is lexically scoped, it does not carry over to the invocation of functions declared outside of an `unsafe` context:
416-
417-
```javascript
418-
function increment(ctr) {
419-
ctr.value++; // error due to illegal read/write of `ctr.value` outside of `unsafe`
420-
}
421-
unsafe {
422-
const ctr = new Counter();
423-
increment(ctr);
424-
}
425-
```
426-
427-
Thread-safe code may execute `unsafe` code without restriction, and `unsafe` code may do likewise. As `unsafe` already indicates a transition boundary between thread-safe and `unsafe` code, there is no need to declare all calling code `unsafe` as you might need to do for `async`/`await`. The `unsafe` keyword itself does not entail any implicit synchronization or coordination as that would be in opposition to our performance goals. Instead, the onus is on developers to be cognizant of thread safety concerns when they define an `unsafe` block. As such, a developer can choose the coordination mechanism that best suits the needs of their application, be that a `Mutex`, a lock-free concurrent deque, etc.
428-
429364
## Open Questions
430365
431366
### Attaching methods to shared structs

0 commit comments

Comments
 (0)