Skip to content

Commit af864c1

Browse files
authored
Improve holey array handling and remove Array.create (#855)
1 parent 1e8a5f9 commit af864c1

27 files changed

+843
-1080
lines changed

src/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5865,7 +5865,7 @@ export class Compiler extends DiagnosticEmitter {
58655865
}
58665866

58675867
// apply concrete types to the generic function signature
5868-
let resolvedTypeArguments = Array.create<Type>(numTypeParameters);
5868+
let resolvedTypeArguments = new Array<Type>(numTypeParameters);
58695869
for (let i = 0; i < numTypeParameters; ++i) {
58705870
let name = typeParameterNodes[i].name.text;
58715871
if (contextualTypeArguments.has(name)) {

std/assembly/array.ts

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export class Array<T> extends ArrayBufferView {
3131
// to work with typed and normal arrays interchangeably. Technically, normal arrays do not need
3232
// `dataStart` (equals `data`) and `dataLength` (equals computed `data.byteLength`).
3333

34-
// Also note that Array<T> with non-nullable T must guard against implicit null values whenever
35-
// length is modified in a way that a null value would exist. Otherwise, the compiler wouldn't be
36-
// able to guarantee type-safety anymore. For lack of a better word, such an array is "holey".
34+
// Also note that Array<T> with non-nullable T must guard against uninitialized null values
35+
// whenever an element is accessed. Otherwise, the compiler wouldn't be able to guarantee
36+
// type-safety anymore. For lack of a better word, such an array is "holey".
3737

3838
private length_: i32;
3939

@@ -42,20 +42,14 @@ export class Array<T> extends ArrayBufferView {
4242
}
4343

4444
static create<T>(capacity: i32 = 0): Array<T> {
45-
if (<u32>capacity > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
46-
var array = changetype<Array<T>>(__allocArray(capacity, alignof<T>(), idof<T[]>())); // retains
47-
changetype<Array<T>>(array).length_ = 0; // safe even if T is a non-nullable reference
48-
memory.fill(array.dataStart, 0, <usize>array.dataLength);
45+
WARNING("'Array.create' is deprecated. Use 'new Array' instead, making sure initial elements are initialized.");
46+
var array = new Array<T>(capacity);
47+
array.length = 0;
4948
return array;
5049
}
5150

5251
constructor(length: i32 = 0) {
5352
super(length, alignof<T>());
54-
if (isReference<T>()) {
55-
if (!isNullable<T>()) {
56-
if (length) throw new Error(E_HOLEYARRAY);
57-
}
58-
}
5953
this.length_ = length;
6054
}
6155

@@ -69,19 +63,17 @@ export class Array<T> extends ArrayBufferView {
6963

7064
set length(newLength: i32) {
7165
var oldLength = this.length_;
72-
if (isReference<T>()) {
73-
if (!isNullable<T>()) {
74-
if (<u32>newLength > <u32>oldLength) throw new Error(E_HOLEYARRAY);
75-
}
76-
}
77-
ensureSize(changetype<usize>(this), newLength, alignof<T>());
78-
if (isManaged<T>()) { // release no longer used refs
79-
if (oldLength > newLength) {
80-
let dataStart = this.dataStart;
81-
do __release(load<usize>(dataStart + (<usize>--oldLength << alignof<T>())));
82-
while (oldLength > newLength);
83-
// no need to zero memory on shrink -> is zeroed on grow
66+
if (isManaged<T>()) {
67+
if (oldLength > newLength) { // release no longer used refs
68+
let cur = (<usize>newLength << alignof<T>());
69+
let end = (<usize>oldLength << alignof<T>());
70+
do __release(load<usize>(cur));
71+
while ((cur += sizeof<T>()) < end);
72+
} else {
73+
ensureSize(changetype<usize>(this), newLength, alignof<T>());
8474
}
75+
} else {
76+
ensureSize(changetype<usize>(this), newLength, alignof<T>());
8577
}
8678
this.length_ = newLength;
8779
}
@@ -101,35 +93,30 @@ export class Array<T> extends ArrayBufferView {
10193
}
10294

10395
@operator("[]") private __get(index: i32): T {
96+
if (<u32>index >= <u32>this.length_) throw new RangeError(E_INDEXOUTOFRANGE);
97+
var value = this.__unchecked_get(index);
10498
if (isReference<T>()) {
10599
if (!isNullable<T>()) {
106-
if (<u32>index >= <u32>this.length_) throw new Error(E_HOLEYARRAY);
100+
if (!changetype<usize>(value)) throw new Error(E_HOLEYARRAY);
107101
}
108102
}
109-
if (<u32>index >= <u32>this.dataLength >>> alignof<T>()) throw new RangeError(E_INDEXOUTOFRANGE);
110-
return this.__unchecked_get(index);
103+
return value;
111104
}
112105

113-
@operator("{}") private __unchecked_get(index: i32): T {
106+
@unsafe @operator("{}") private __unchecked_get(index: i32): T {
114107
return load<T>(this.dataStart + (<usize>index << alignof<T>()));
115108
}
116109

117110
@operator("[]=") private __set(index: i32, value: T): void {
118-
var length = this.length_;
119-
if (isReference<T>()) {
120-
if (!isNullable<T>()) {
121-
if (<u32>index > <u32>length) throw new Error(E_HOLEYARRAY);
122-
}
123-
}
124111
ensureSize(changetype<usize>(this), index + 1, alignof<T>());
125112
this.__unchecked_set(index, value);
126-
if (index >= length) this.length_ = index + 1;
113+
if (index >= this.length_) this.length_ = index + 1;
127114
}
128115

129-
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
116+
@unsafe @operator("{}=") private __unchecked_set(index: i32, value: T): void {
130117
if (isManaged<T>()) {
131118
let offset = this.dataStart + (<usize>index << alignof<T>());
132-
let oldRef: usize = load<usize>(offset);
119+
let oldRef = load<usize>(offset);
133120
if (changetype<usize>(value) != oldRef) {
134121
store<usize>(offset, __retain(changetype<usize>(value)));
135122
__release(oldRef);

std/assembly/fixedarray.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ export class FixedArray<T> {
1414

1515
constructor(length: i32) {
1616
if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
17-
if (isReference<T>()) {
18-
if (!isNullable<T>()) {
19-
if (length) throw new Error(E_HOLEYARRAY);
20-
}
21-
}
2217
var outSize = <usize>length << alignof<T>();
2318
var out = __alloc(outSize, idof<FixedArray<T>>());
2419
memory.fill(out, 0, outSize);
@@ -31,25 +26,31 @@ export class FixedArray<T> {
3126

3227
@operator("[]") private __get(index: i32): T {
3328
if (<u32>index >= <u32>this.length) throw new RangeError(E_INDEXOUTOFRANGE);
34-
return this.__unchecked_get(index);
29+
var value = this.__unchecked_get(index);
30+
if (isReference<T>()) {
31+
if (!isNullable<T>()) {
32+
if (!changetype<usize>(value)) throw new Error(E_HOLEYARRAY);
33+
}
34+
}
35+
return value;
36+
}
37+
38+
@unsafe @operator("{}") private __unchecked_get(index: i32): T {
39+
return load<T>(changetype<usize>(this) + (<usize>index << alignof<T>()));
3540
}
3641

3742
@operator("[]=") private __set(index: i32, value: T): void {
3843
if (<u32>index >= <u32>this.length) throw new RangeError(E_INDEXOUTOFRANGE);
3944
this.__unchecked_set(index, value);
4045
}
4146

42-
@operator("{}") private __unchecked_get(index: i32): T {
43-
return load<T>(changetype<usize>(this) + (<usize>index << alignof<T>()));
44-
}
45-
46-
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
47+
@unsafe @operator("{}=") private __unchecked_set(index: i32, value: T): void {
4748
if (isManaged<T>()) {
4849
let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
49-
let oldValue = load<usize>(offset);
50-
if (changetype<usize>(value) != oldValue) {
50+
let oldRef = load<usize>(offset);
51+
if (changetype<usize>(value) != oldRef) {
5152
store<usize>(offset, __retain(changetype<usize>(value)));
52-
__release(changetype<usize>(oldValue));
53+
__release(changetype<usize>(oldRef));
5354
}
5455
} else {
5556
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);

std/assembly/index.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,13 +1249,11 @@ declare class Array<T> {
12491249

12501250
/** Tests if a value is an array. */
12511251
static isArray<U>(value: any): value is Array<any>;
1252-
/** Creates a new array with at least the specified capacity and length zero. */
1253-
static create<T>(capacity?: i32): Array<T>;
12541252

12551253
[key: number]: T;
12561254
/** Current length of the array. */
12571255
length: i32;
1258-
/** Constructs a new array. If length is greater than zero and T is a non-nullable reference, use `Array.create` instead.*/
1256+
/** Constructs a new array. */
12591257
constructor(capacity?: i32);
12601258

12611259
fill(value: T, start?: i32, end?: i32): this;

std/assembly/map.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,27 +197,33 @@ export class Map<K,V> {
197197
// FIXME: this is preliminary, needs iterators/closures
198198
var start = changetype<usize>(this.entries);
199199
var size = this.entriesOffset;
200-
var keys = Array.create<K>(size);
200+
var keys = new Array<K>(size);
201+
var length = 0;
201202
for (let i = 0; i < size; ++i) {
202203
let entry = changetype<MapEntry<K,V>>(start + <usize>i * ENTRY_SIZE<K,V>());
203204
if (!(entry.taggedNext & EMPTY)) {
204205
keys.push(entry.key);
206+
++length;
205207
}
206208
}
209+
keys.length = length;
207210
return keys;
208211
}
209212

210213
values(): V[] {
211214
// FIXME: this is preliminary, needs iterators/closures
212215
var start = changetype<usize>(this.entries);
213216
var size = this.entriesOffset;
214-
var values = Array.create<V>(size);
217+
var values = new Array<V>(size);
218+
var length = 0;
215219
for (let i = 0; i < size; ++i) {
216220
let entry = changetype<MapEntry<K,V>>(start + <usize>i * ENTRY_SIZE<K,V>());
217221
if (!(entry.taggedNext & EMPTY)) {
218222
values.push(entry.value);
223+
++length;
219224
}
220225
}
226+
values.length = length;
221227
return values;
222228
}
223229

std/assembly/set.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,16 @@ export class Set<T> {
174174
// FIXME: this is preliminary, needs iterators/closures
175175
var start = changetype<usize>(this.entries);
176176
var size = this.entriesOffset;
177-
var values = Array.create<T>(size);
177+
var values = new Array<T>(size);
178+
var length = 0;
178179
for (let i = 0; i < size; ++i) {
179180
let entry = changetype<SetEntry<T>>(start + <usize>i * ENTRY_SIZE<T>());
180181
if (!(entry.taggedNext & EMPTY)) {
181182
values.push(entry.key);
183+
++length;
182184
}
183185
}
186+
values.length = length;
184187
return values;
185188
}
186189

std/portable/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,6 @@ declare class DataView {
379379
declare class Array<T> {
380380

381381
static isArray<U>(value: any): value is Array<any>;
382-
static create<T>(capacity?: i32): Array<T>;
383382

384383
[key: number]: T;
385384
length: i32;

std/portable/index.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,6 @@ globalScope["isArrayLike"] = function isArrayLike(expr) {
237237
&& Math.trunc(expr.length) === expr.length;
238238
};
239239

240-
Array.create = function(capacity) {
241-
var arr = new Array(capacity);
242-
arr.length = 0;
243-
return arr;
244-
};
245-
246240
globalScope["isDefined"] = function isDefined(expr) {
247241
return typeof expr !== "undefined";
248242
}

tests/compiler/assert-nonnull.optimized.wat

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
(type $FUNCSIG$v (func))
66
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
77
(memory $0 1)
8-
(data (i32.const 8) "^\00\00\00\01\00\00\00\01\00\00\00^\00\00\00E\00l\00e\00m\00e\00n\00t\00 \00t\00y\00p\00e\00 \00m\00u\00s\00t\00 \00b\00e\00 \00n\00u\00l\00l\00a\00b\00l\00e\00 \00i\00f\00 \00a\00r\00r\00a\00y\00 \00i\00s\00 \00h\00o\00l\00e\00y")
9-
(data (i32.const 120) "\1a\00\00\00\01\00\00\00\01\00\00\00\1a\00\00\00~\00l\00i\00b\00/\00a\00r\00r\00a\00y\00.\00t\00s")
10-
(data (i32.const 168) "$\00\00\00\01\00\00\00\01\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e")
8+
(data (i32.const 8) "$\00\00\00\01\00\00\00\01\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e")
9+
(data (i32.const 64) "\1a\00\00\00\01\00\00\00\01\00\00\00\1a\00\00\00~\00l\00i\00b\00/\00a\00r\00r\00a\00y\00.\00t\00s")
10+
(data (i32.const 112) "^\00\00\00\01\00\00\00\01\00\00\00^\00\00\00E\00l\00e\00m\00e\00n\00t\00 \00t\00y\00p\00e\00 \00m\00u\00s\00t\00 \00b\00e\00 \00n\00u\00l\00l\00a\00b\00l\00e\00 \00i\00f\00 \00a\00r\00r\00a\00y\00 \00i\00s\00 \00h\00o\00l\00e\00y")
1111
(table $0 1 funcref)
1212
(elem (i32.const 0) $null)
1313
(global $~lib/argc (mut i32) (i32.const 0))
@@ -63,28 +63,25 @@
6363
i32.ge_u
6464
if
6565
i32.const 24
66-
i32.const 136
67-
i32.const 106
68-
i32.const 45
66+
i32.const 80
67+
i32.const 96
68+
i32.const 41
6969
call $~lib/builtins/abort
7070
unreachable
7171
end
72-
i32.const 0
7372
local.get $0
74-
i32.load offset=8
75-
i32.const 2
76-
i32.shr_u
77-
i32.ge_u
73+
call $~lib/array/Array<assert-nonnull/Foo>#__unchecked_get
74+
local.tee $0
75+
i32.eqz
7876
if
79-
i32.const 184
80-
i32.const 136
81-
i32.const 109
82-
i32.const 61
77+
i32.const 128
78+
i32.const 80
79+
i32.const 100
80+
i32.const 39
8381
call $~lib/builtins/abort
8482
unreachable
8583
end
8684
local.get $0
87-
call $~lib/array/Array<assert-nonnull/Foo>#__unchecked_get
8885
)
8986
(func $assert-nonnull/testArr (; 6 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
9087
local.get $0
@@ -98,15 +95,13 @@
9895
(func $~lib/array/Array<assert-nonnull/Foo | null>#__get (; 7 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
9996
i32.const 0
10097
local.get $0
101-
i32.load offset=8
102-
i32.const 2
103-
i32.shr_u
98+
i32.load offset=12
10499
i32.ge_u
105100
if
106-
i32.const 184
107-
i32.const 136
108-
i32.const 109
109-
i32.const 61
101+
i32.const 24
102+
i32.const 80
103+
i32.const 96
104+
i32.const 41
110105
call $~lib/builtins/abort
111106
unreachable
112107
end

0 commit comments

Comments
 (0)