Fix some map issues; Simplify internal ArrayBuffer API a bit

This commit is contained in:
dcodeIO 2018-06-20 15:51:47 +02:00
parent 48e96cbcf5
commit dd4be7b693
14 changed files with 17159 additions and 1132 deletions

2
dist/asc.js vendored

File diff suppressed because one or more lines are too long

2
dist/asc.js.map vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -99,6 +99,17 @@ export enum NodeKind {
COMMENT COMMENT
} }
/** Checks if a node represents a constant value. */
export function nodeIsConstantValue(kind: NodeKind): bool {
switch (kind) {
case NodeKind.LITERAL:
case NodeKind.NULL:
case NodeKind.TRUE:
case NodeKind.FALSE: return true;
}
return false;
}
/** Base class of all nodes. */ /** Base class of all nodes. */
export abstract class Node { export abstract class Node {

View File

@ -126,7 +126,9 @@ import {
StringLiteralExpression, StringLiteralExpression,
UnaryPostfixExpression, UnaryPostfixExpression,
UnaryPrefixExpression, UnaryPrefixExpression,
FieldDeclaration FieldDeclaration,
nodeIsConstantValue
} from "./ast"; } from "./ast";
import { import {
@ -5503,9 +5505,7 @@ export class Compiler extends DiagnosticEmitter {
let allOptionalsAreConstant = true; let allOptionalsAreConstant = true;
for (let i = numArguments; i < maxArguments; ++i) { for (let i = numArguments; i < maxArguments; ++i) {
let initializer = parameterNodes[i].initializer; let initializer = parameterNodes[i].initializer;
if (!(initializer && initializer.kind == NodeKind.LITERAL)) { if (!(initializer !== null && nodeIsConstantValue(initializer.kind))) {
// TODO: other kinds might be constant as well
// NOTE: if the initializer is missing this is reported in ensureTrampoline below
allOptionalsAreConstant = false; allOptionalsAreConstant = false;
break; break;
} }

View File

@ -3505,34 +3505,33 @@ export class ClassPrototype extends Element {
fieldDeclaration.type, fieldDeclaration.type,
instance.contextualTypeArguments instance.contextualTypeArguments
); );
if (fieldType) { if (!fieldType) break;
let fieldInstance = new Field( let fieldInstance = new Field(
<FieldPrototype>member, <FieldPrototype>member,
internalName + INSTANCE_DELIMITER + (<FieldPrototype>member).simpleName, internalName + INSTANCE_DELIMITER + (<FieldPrototype>member).simpleName,
fieldType, fieldType,
fieldDeclaration, fieldDeclaration,
instance instance
); );
switch (fieldType.byteSize) { // align switch (fieldType.byteSize) { // align
case 1: break; case 1: break;
case 2: { case 2: {
if (memoryOffset & 1) ++memoryOffset; if (memoryOffset & 1) ++memoryOffset;
break; break;
}
case 4: {
if (memoryOffset & 3) memoryOffset = (memoryOffset | 3) + 1;
break;
}
case 8: {
if (memoryOffset & 7) memoryOffset = (memoryOffset | 7) + 1;
break;
}
default: assert(false);
} }
fieldInstance.memoryOffset = memoryOffset; case 4: {
memoryOffset += fieldType.byteSize; if (memoryOffset & 3) memoryOffset = (memoryOffset | 3) + 1;
instance.members.set(member.simpleName, fieldInstance); break;
}
case 8: {
if (memoryOffset & 7) memoryOffset = (memoryOffset | 7) + 1;
break;
}
default: assert(false);
} }
fieldInstance.memoryOffset = memoryOffset;
memoryOffset += fieldType.byteSize;
instance.members.set(member.simpleName, fieldInstance);
break; break;
} }

2
std/assembly.d.ts vendored
View File

@ -307,7 +307,7 @@ declare class ArrayBuffer {
/** The size, in bytes, of the array. */ /** The size, in bytes, of the array. */
readonly byteLength: i32; readonly byteLength: i32;
/** Constructs a new array buffer of the given length in bytes. */ /** Constructs a new array buffer of the given length in bytes. */
constructor(length: i32); constructor(length: i32, unsafe?: bool);
/** Returns a copy of this array buffer's bytes from begin, inclusive, up to end, exclusive. */ /** Returns a copy of this array buffer's bytes from begin, inclusive, up to end, exclusive. */
slice(begin?: i32, end?: i32): ArrayBuffer; slice(begin?: i32, end?: i32): ArrayBuffer;
} }

View File

@ -9,10 +9,10 @@ export class ArrayBuffer {
readonly byteLength: i32; // capped to [0, MAX_LENGTH] readonly byteLength: i32; // capped to [0, MAX_LENGTH]
constructor(length: i32) { constructor(length: i32, unsafe: bool = false) {
if (<u32>length > <u32>MAX_BLENGTH) throw new RangeError("Invalid array buffer length"); if (<u32>length > <u32>MAX_BLENGTH) throw new RangeError("Invalid array buffer length");
var buffer = allocUnsafe(length); var buffer = allocUnsafe(length);
set_memory(changetype<usize>(buffer) + HEADER_SIZE, 0, <usize>length); if (!unsafe) set_memory(changetype<usize>(buffer) + HEADER_SIZE, 0, <usize>length);
return buffer; return buffer;
} }
@ -27,4 +27,16 @@ export class ArrayBuffer {
move_memory(changetype<usize>(buffer) + HEADER_SIZE, changetype<usize>(this) + HEADER_SIZE + begin, newLen); move_memory(changetype<usize>(buffer) + HEADER_SIZE, changetype<usize>(this) + HEADER_SIZE + begin, newLen);
return buffer; return buffer;
} }
// internals
static readonly HEADER_SIZE: usize = HEADER_SIZE;
@inline load<T>(index: i32): T {
return load<T>(changetype<usize>(this) + index * sizeof<T>(), HEADER_SIZE);
}
@inline store<T>(index: i32, value: T): void {
store<T>(changetype<usize>(this) + index * sizeof<T>(), value, HEADER_SIZE);
}
} }

View File

@ -1,9 +1,8 @@
(module (module
(type $iii (func (param i32 i32) (result i32))) (type $iiii (func (param i32 i32 i32) (result i32)))
(type $iiiiv (func (param i32 i32 i32 i32))) (type $iiiiv (func (param i32 i32 i32 i32)))
(type $ii (func (param i32) (result i32))) (type $ii (func (param i32) (result i32)))
(type $iiiv (func (param i32 i32 i32))) (type $iiiv (func (param i32 i32 i32)))
(type $iiii (func (param i32 i32 i32) (result i32)))
(type $v (func)) (type $v (func))
(import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32))) (import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32)))
(global $~lib/allocator/arena/startOffset (mut i32) (i32.const 0)) (global $~lib/allocator/arena/startOffset (mut i32) (i32.const 0))
@ -477,8 +476,8 @@
) )
) )
) )
(func $~lib/arraybuffer/ArrayBuffer#constructor (; 5 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (func $~lib/arraybuffer/ArrayBuffer#constructor (; 5 ;) (type $iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(local $2 i32) (local $3 i32)
(if (if
(i32.gt_u (i32.gt_u
(get_local $1) (get_local $1)
@ -494,19 +493,28 @@
(unreachable) (unreachable)
) )
) )
(call $~lib/memory/set_memory (set_local $3
(i32.add (call $~lib/internal/arraybuffer/allocUnsafe
(tee_local $2 (get_local $1)
(call $~lib/internal/arraybuffer/allocUnsafe
(get_local $1)
)
)
(i32.const 8)
) )
(i32.const 0)
(get_local $1)
) )
(get_local $2) (if
(i32.eqz
(i32.and
(get_local $2)
(i32.const 1)
)
)
(call $~lib/memory/set_memory
(i32.add
(get_local $3)
(i32.const 8)
)
(i32.const 0)
(get_local $1)
)
)
(get_local $3)
) )
(func $~lib/memory/copy_memory (; 6 ;) (type $iiiv) (param $0 i32) (param $1 i32) (param $2 i32) (func $~lib/memory/copy_memory (; 6 ;) (type $iiiv) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32) (local $3 i32)
@ -2331,6 +2339,7 @@
(call $~lib/arraybuffer/ArrayBuffer#constructor (call $~lib/arraybuffer/ArrayBuffer#constructor
(i32.const 0) (i32.const 0)
(i32.const 8) (i32.const 8)
(i32.const 0)
) )
) )
(if (if

View File

@ -1,9 +1,8 @@
(module (module
(type $iii (func (param i32 i32) (result i32))) (type $iiii (func (param i32 i32 i32) (result i32)))
(type $iiiiv (func (param i32 i32 i32 i32))) (type $iiiiv (func (param i32 i32 i32 i32)))
(type $ii (func (param i32) (result i32))) (type $ii (func (param i32) (result i32)))
(type $iiiv (func (param i32 i32 i32))) (type $iiiv (func (param i32 i32 i32)))
(type $iiii (func (param i32 i32 i32) (result i32)))
(type $v (func)) (type $v (func))
(import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32))) (import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32)))
(global $~lib/internal/allocator/AL_BITS i32 (i32.const 3)) (global $~lib/internal/allocator/AL_BITS i32 (i32.const 3))
@ -539,8 +538,8 @@
) )
) )
) )
(func $~lib/arraybuffer/ArrayBuffer#constructor (; 5 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (func $~lib/arraybuffer/ArrayBuffer#constructor (; 5 ;) (type $iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(local $2 i32) (local $3 i32)
(if (if
(i32.gt_u (i32.gt_u
(get_local $1) (get_local $1)
@ -556,21 +555,29 @@
(unreachable) (unreachable)
) )
) )
(set_local $2 (set_local $3
(call $~lib/internal/arraybuffer/allocUnsafe (call $~lib/internal/arraybuffer/allocUnsafe
(get_local $1) (get_local $1)
) )
) )
(call $~lib/memory/set_memory (if
(i32.add (i32.eqz
(get_local $2) (i32.and
(i32.const 8) (get_local $2)
(i32.const 1)
)
)
(call $~lib/memory/set_memory
(i32.add
(get_local $3)
(i32.const 8)
)
(i32.const 0)
(get_local $1)
) )
(i32.const 0)
(get_local $1)
) )
(return (return
(get_local $2) (get_local $3)
) )
) )
(func $~lib/memory/copy_memory (; 6 ;) (type $iiiv) (param $0 i32) (param $1 i32) (param $2 i32) (func $~lib/memory/copy_memory (; 6 ;) (type $iiiv) (param $0 i32) (param $1 i32) (param $2 i32)
@ -2863,6 +2870,7 @@
(call $~lib/arraybuffer/ArrayBuffer#constructor (call $~lib/arraybuffer/ArrayBuffer#constructor
(i32.const 0) (i32.const 0)
(i32.const 8) (i32.const 8)
(i32.const 0)
) )
) )
(if (if

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,3 @@
import {
allocUnsafe,
HEADER_SIZE,
MAX_BLENGTH
} from "internal/arraybuffer";
import { import {
hash hash
} from "internal/hash"; } from "internal/hash";
@ -25,44 +19,50 @@ class MapEntry<K,V> {
/** Empty bit. */ /** Empty bit. */
const EMPTY: usize = 1 << 0; const EMPTY: usize = 1 << 0;
/** Computes the alignment of an entry. */
@inline function ENTRY_ALIGN<K,V>(): usize {
// can align to 4 instead of 8 if 32-bit and K/V is <= 32-bits
const maxkv = sizeof<K>() > sizeof<V>() ? sizeof<K>() : sizeof<V>();
const align = (maxkv > sizeof<usize>() ? maxkv : sizeof<usize>()) - 1;
return align;
}
/** Computes the aligned size of an entry. */
@inline function ENTRY_SIZE<K,V>(): usize {
const align = ENTRY_ALIGN<K,V>();
const size = (offsetof<MapEntry<K,V>>() + align) & ~align;
return size;
}
class Map<K,V> { class Map<K,V> {
/** A buffer storing `indexMask + 1` indices followed by `capacity` entries. */ // buckets holding references to the respective first entry within
private buffer: ArrayBuffer; private buckets: ArrayBuffer; // usize[bucketsMask + 1]
/** The current index mask for distributing hash codes among indixes. */ private bucketsMask: u32;
private indexMask: u32;
/** Maximum number of entries this map can hold before rehashing. */
private capacity: i32;
/** Entry insertion offset. */
private offset: i32;
/** Number of entries excl. explicitly marked empty ones. */
private count: i32;
/** Size of a single index. */ // entries in insertion order
private readonly INDEX_SIZE: usize = sizeof<usize>(); private entries: ArrayBuffer; // MapEntry<K,V>[entriesCapacity]
/** Size of a single entry. */ private entriesCapacity: i32;
private readonly ENTRY_SIZE: usize = (offsetof<MapEntry<K,V>>() + 7) & ~7; private entriesOffset: i32;
private entriesCount: i32;
get size(): i32 { return this.count; } get size(): i32 { return this.entriesCount; }
constructor() { this.clear(); } constructor() { this.clear(); }
clear(): void { clear(): void {
const bufferSize = INITIAL_CAPACITY * <i32>(this.INDEX_SIZE + this.ENTRY_SIZE); // TODO: readonly ^= const const bucketsSize = INITIAL_CAPACITY * <i32>sizeof<usize>();
var buffer = allocUnsafe(bufferSize); this.buckets = new ArrayBuffer(bucketsSize);
set_memory(changetype<usize>(buffer) + HEADER_SIZE, 0, INITIAL_CAPACITY * this.INDEX_SIZE); this.bucketsMask = INITIAL_CAPACITY - 1;
this.buffer = buffer; const entriesSize = INITIAL_CAPACITY * <i32>ENTRY_SIZE<K,V>();
this.indexMask = INITIAL_CAPACITY - 1; this.entries = new ArrayBuffer(entriesSize, true);
this.capacity = INITIAL_CAPACITY; this.entriesCapacity = INITIAL_CAPACITY;
this.offset = 0; this.entriesOffset = 0;
this.count = 0; this.entriesCount = 0;
} }
private find(key: K, hashCode: u32): MapEntry<K,V> | null { private find(key: K, hashCode: u32): MapEntry<K,V> | null {
var entry = load<MapEntry<K,V>>( var entry = this.buckets.load<MapEntry<K,V>>(hashCode & this.bucketsMask);
changetype<usize>(this.buffer) + (hashCode & this.indexMask) * this.INDEX_SIZE,
HEADER_SIZE
);
while (entry) { while (entry) {
if (!(entry.taggedNext & EMPTY) && entry.key == key) return entry; if (!(entry.taggedNext & EMPTY) && entry.key == key) return entry;
entry = changetype<MapEntry<K,V>>(entry.taggedNext & ~EMPTY); entry = changetype<MapEntry<K,V>>(entry.taggedNext & ~EMPTY);
@ -84,124 +84,130 @@ class Map<K,V> {
var entry = this.find(key, hashCode); var entry = this.find(key, hashCode);
if (entry) { if (entry) {
entry.value = value; entry.value = value;
} else { // check if rehashing is necessary } else {
let capacity = this.capacity; // check if rehashing is necessary
if (this.offset == capacity) { let capacity = this.entriesCapacity;
if (this.entriesOffset == capacity) {
this.rehash( this.rehash(
this.count >= <i32>(capacity * FREE_FACTOR) this.entriesCount >= <i32>(capacity * FREE_FACTOR)
? (this.indexMask << 1) | 1 // grow to next pwr 2 ? (this.bucketsMask << 1) | 1 // grow capacity to next 2^N
: this.indexMask // just rehash if 1/4+ entries are empty : this.bucketsMask // just rehash if 1/4+ entries are empty
); );
capacity = this.capacity; capacity = this.entriesCapacity;
} }
// append new entry // append new entry
let buffer = this.buffer; let entries = this.entries;
entry = changetype<MapEntry<K,V>>( entry = changetype<MapEntry<K,V>>(
changetype<usize>(buffer) + HEADER_SIZE changetype<usize>(entries) + ArrayBuffer.HEADER_SIZE + this.entriesOffset++ * ENTRY_SIZE<K,V>()
+ (this.indexMask + 1) * this.INDEX_SIZE
+ this.offset++ * this.ENTRY_SIZE
); );
entry.key = key; entry.key = key;
entry.value = value; entry.value = value;
// link with previous entry in bucket
// link with previous colliding entry, if any let bucketIndex = hashCode & this.bucketsMask;
let tableIndex = hashCode & this.indexMask; entry.taggedNext = this.buckets.load<usize>(bucketIndex);
let entryOffset = changetype<usize>(buffer) + HEADER_SIZE + (hashCode & this.indexMask) * this.INDEX_SIZE; this.buckets.store<usize>(bucketIndex, changetype<usize>(entry));
entry.taggedNext = load<usize>(entryOffset); ++this.entriesCount;
store<usize>(entryOffset, changetype<usize>(entry));
++this.count;
} }
} }
delete(key: K): bool { delete(key: K): bool {
var entry = this.find(key, hash(key)); var entry = this.find(key, hash(key));
if (!entry) return false; if (!entry) return false;
--this.count;
entry.taggedNext |= EMPTY; entry.taggedNext |= EMPTY;
if (this.indexMask > <u32>INITIAL_CAPACITY && this.count < <i32>(this.offset * FREE_FACTOR)) { --this.entriesCount;
this.rehash(this.indexMask >> 1); if (
} this.bucketsMask > <u32>INITIAL_CAPACITY &&
this.entriesCount < <i32>(this.entriesOffset * FREE_FACTOR)
) this.rehash(this.bucketsMask >> 1);
return true; return true;
} }
private rehash(newMask: i32): void { private rehash(newBucketsMask: i32): void {
// TODO: check capacity var newBucketsCapacity = newBucketsMask + 1;
var newIndices = newMask + 1; var newBuckets = new ArrayBuffer(newBucketsCapacity * sizeof<usize>());
var newCapacity = <i32>(newIndices * FILL_FACTOR); var newEntriesCapacity = <i32>(newBucketsCapacity * FILL_FACTOR);
var newBufferSize = newIndices * this.INDEX_SIZE + newCapacity * this.ENTRY_SIZE; var newEntries = new ArrayBuffer(newEntriesCapacity * ENTRY_SIZE<K,V>(), true);
var newBuffer = allocUnsafe(newBufferSize);
set_memory(changetype<usize>(newBuffer) + HEADER_SIZE, 0, newIndices * this.INDEX_SIZE); // copy old entries to new entries
var src = changetype<MapEntry<K,V>>( var p = changetype<usize>(this.entries) + ArrayBuffer.HEADER_SIZE;
changetype<usize>(this.buffer) + HEADER_SIZE var q = changetype<usize>(newEntries) + ArrayBuffer.HEADER_SIZE;
+ (this.indexMask + 1) * this.INDEX_SIZE var k = p + this.entriesOffset * ENTRY_SIZE<K,V>();
); while (p != k) {
var dst = changetype<MapEntry<K,V>>( let pEntry = changetype<MapEntry<K,V>>(p);
changetype<usize>(newBuffer) + HEADER_SIZE let qEntry = changetype<MapEntry<K,V>>(q);
+ newIndices * this.INDEX_SIZE if (!(pEntry.taggedNext & EMPTY)) {
); qEntry.key = pEntry.key;
var end = changetype<usize>(src) + this.offset * this.ENTRY_SIZE; qEntry.value = pEntry.value;
while (changetype<usize>(src) != end) { let bucketIndex = hash(pEntry.key) & newBucketsMask;
if (!(src.taggedNext & EMPTY)) { qEntry.taggedNext = newBuckets.load<usize>(bucketIndex);
dst.key = src.key; newBuckets.store<MapEntry<K,V>>(bucketIndex, qEntry);
dst.value = src.value; q += ENTRY_SIZE<K,V>();
let oldOffset = (
changetype<usize>(newBuffer) /* + HEADER_SIZE -> constantOffset */
+ (hash(src.key) & newMask) * this.INDEX_SIZE
);
dst.taggedNext = load<usize>(
oldOffset,
HEADER_SIZE
);
store<MapEntry<K,V>>(
oldOffset,
dst,
HEADER_SIZE
);
dst = changetype<MapEntry<K,V>>(changetype<usize>(dst) + this.ENTRY_SIZE);
} }
src = changetype<MapEntry<K,V>>(changetype<usize>(src) + this.ENTRY_SIZE); p += ENTRY_SIZE<K,V>();
} }
this.buffer = newBuffer;
this.indexMask = newMask; this.buckets = newBuckets;
this.capacity = <i32>newCapacity; this.bucketsMask = newBucketsMask;
this.offset = this.count; this.entries = newEntries;
this.entriesCapacity = newEntriesCapacity;
this.entriesOffset = this.entriesCount;
} }
} }
import "allocator/arena"; import "allocator/arena";
var map = new Map<i32,i32>(); function test<K,V>(): void {
var map = new Map<K,V>();
// insert new // insert new
for (let i = 1; i <= 200; ++i) { for (let k: K = 1; k <= 200; ++k) {
map.set(i, 100 + i); map.set(k, 100 + <V>k);
assert(map.has(i)); assert(map.has(k));
assert(!map.has(i + 1)); assert(!map.has(k + 1));
assert(map.get(i) == 100 + i); assert(map.get(k) == 100 + k);
}
assert(map.size == 200);
// insert duplicate
for (let k: K = 50; k <= 100; ++k) {
assert(map.has(k));
assert(map.get(k) == 100 + <V>k);
map.set(k, 100 + <V>k);
assert(map.has(k));
assert(map.get(k) == 100 + <V>k);
}
assert(map.size == 200);
// delete
for (let k: K = 1; k <= 100; ++k) {
assert(map.has(k));
assert(map.get(k) == 100 + <V>k);
map.delete(k);
assert(!map.has(k));
assert(map.has(k + 1));
}
assert(map.size == 100);
// insert + delete
for (let k: K = 1; k <= 50; ++k) {
assert(!map.has(k));
map.set(k, 100 + <V>k);
assert(map.has(k));
map.delete(k);
assert(!map.has(k));
}
assert(map.size == 100);
// clear
map.clear();
assert(map.size == 0);
} }
assert(map.size == 200);
// insert duplicate test<i32,i32>();
for (let i = 50; i <= 100; ++i) { test<i64,i32>();
assert(map.has(i)); test<i64,i64>();
assert(map.get(i) == 100 + i); test<i32,i64>();
map.set(i, 100 + i); test<i16,i32>();
assert(map.has(i)); test<i16,i64>();
assert(map.get(i) == 100 + i); test<i32,i16>();
} test<i64,i16>();
assert(map.size == 200);
// delete
for (let i = 1; i <= 100; ++i) {
assert(map.has(i));
assert(map.get(i) == 100 + i);
map.delete(i);
assert(!map.has(i));
assert(map.has(i + 1));
}
assert(map.size == 100);
// clear
map.clear();
assert(map.size == 0);

File diff suppressed because it is too large Load Diff