Skip to content

Better wasm support #232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion sqlite3/lib/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ export 'src/result_set.dart';
export 'src/sqlite3.dart';
export 'src/statement.dart'
show CommonPreparedStatement, StatementParameters, CustomStatementParameter;
export 'src/vfs.dart';
33 changes: 23 additions & 10 deletions sqlite3/lib/src/wasm/bindings.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:js_interop';
import 'dart:typed_data';

import 'package:sqlite3/src/vfs.dart';
import 'package:sqlite3/src/wasm/vfs.dart';

import '../constants.dart';
import '../functions.dart';
import '../implementation/bindings.dart';
import 'js_interop/typed_data.dart';
import 'wasm_interop.dart' as wasm;
import 'wasm_interop.dart';

Expand Down Expand Up @@ -134,7 +136,7 @@ final class WasmDatabase extends RawSqliteDatabase {

@override
RawStatementCompiler newCompiler(List<int> utf8EncodedSql) {
final ptr = bindings.allocateBytes(utf8EncodedSql);
final ptr = bindings.allocateBytes(_viewOrCopyByteList(utf8EncodedSql));

return WasmStatementCompiler(this, ptr);
}
Expand All @@ -148,7 +150,8 @@ final class WasmDatabase extends RawSqliteDatabase {
required int eTextRep,
required RawCollation collation,
}) {
final ptr = bindings.allocateBytes(collationName, additionalLength: 1);
final ptr = bindings.allocateBytes(SafeU8Array(collationName.toJS),
additionalLength: 1);
final result = bindings.create_collation(
db,
ptr,
Expand All @@ -170,7 +173,8 @@ final class WasmDatabase extends RawSqliteDatabase {
RawXStep? xStep,
RawXFinal? xFinal,
}) {
final ptr = bindings.allocateBytes(functionName, additionalLength: 1);
final ptr = bindings.allocateBytes(SafeU8Array(functionName.toJS),
additionalLength: 1);

final int result;
if (xFunc != null) {
Expand Down Expand Up @@ -209,7 +213,8 @@ final class WasmDatabase extends RawSqliteDatabase {
required RawXFinal xValue,
required RawXStep xInverse,
}) {
final ptr = bindings.allocateBytes(functionName, additionalLength: 1);
final ptr = bindings.allocateBytes(SafeU8Array(functionName.toJS),
additionalLength: 1);
final result = bindings.create_window_function(
db,
ptr,
Expand Down Expand Up @@ -319,7 +324,7 @@ final class WasmStatement extends RawSqliteStatement {

@override
void sqlite3_bind_blob64(int index, List<int> value) {
final ptr = bindings.allocateBytes(value);
final ptr = bindings.allocateBytes(_viewOrCopyByteList(value));
_allocatedArguments.add(ptr);

bindings.sqlite3_bind_blob64(stmt, index, ptr, value.length, 0);
Expand Down Expand Up @@ -373,7 +378,7 @@ final class WasmStatement extends RawSqliteStatement {
@override
void sqlite3_bind_text(int index, String value) {
final encoded = utf8.encode(value);
final ptr = bindings.allocateBytes(encoded);
final ptr = bindings.allocateBytes(SafeU8Array(encoded.toJS));
_allocatedArguments.add(ptr);

bindings.sqlite3_bind_text(stmt, index, ptr, encoded.length, 0);
Expand Down Expand Up @@ -494,7 +499,7 @@ final class WasmContext extends RawSqliteContext {

@override
void sqlite3_result_blob64(List<int> blob) {
final ptr = bindings.allocateBytes(blob);
final ptr = bindings.allocateBytes(_viewOrCopyByteList(blob));

bindings.sqlite3_result_blob64(
context, ptr, blob.length, SqlSpecialDestructor.SQLITE_TRANSIENT);
Expand All @@ -509,7 +514,7 @@ final class WasmContext extends RawSqliteContext {
@override
void sqlite3_result_error(String message) {
final encoded = utf8.encode(message);
final ptr = bindings.allocateBytes(encoded);
final ptr = bindings.allocateBytes(SafeU8Array(encoded.toJS));

bindings.sqlite3_result_error(context, ptr, encoded.length);
bindings.free(ptr);
Expand All @@ -533,7 +538,7 @@ final class WasmContext extends RawSqliteContext {
@override
void sqlite3_result_text(String text) {
final encoded = utf8.encode(text);
final ptr = bindings.allocateBytes(encoded);
final ptr = bindings.allocateBytes(SafeU8Array(encoded.toJS));

bindings.sqlite3_result_text(
context, ptr, encoded.length, SqlSpecialDestructor.SQLITE_TRANSIENT);
Expand Down Expand Up @@ -602,3 +607,11 @@ class WasmValueList extends ListBase<WasmValue> {
throw UnsupportedError('Setting element in WasmValueList');
}
}

SafeU8Array _viewOrCopyByteList(List<int> bytes) {
if (bytes is Uint8List) {
return SafeU8Array(bytes.toJS);
} else {
return SafeU8Array(Uint8List.fromList(bytes).toJS);
}
}
6 changes: 3 additions & 3 deletions sqlite3/lib/src/wasm/js_interop.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:js_interop';
import 'dart:typed_data';

import 'package:web/web.dart' show Blob;
import 'js_interop/typed_data.dart';

// This internal library exports wrappers around newer Web APIs for which no
// up-to-date bindings exist in the Dart SDK.
Expand All @@ -15,8 +15,8 @@ export 'js_interop/typed_data.dart';
export 'js_interop/wasm.dart';

extension ReadBlob on Blob {
Future<ByteBuffer> byteBuffer() async {
Future<SafeBuffer> byteBuffer() async {
final buffer = await arrayBuffer().toDart;
return buffer.toDart;
return SafeBuffer(buffer);
}
}
45 changes: 22 additions & 23 deletions sqlite3/lib/src/wasm/js_interop/atomics.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'dart:typed_data';
import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import 'typed_data.dart';

@JS('Int32Array')
external JSFunction _int32Array;

Expand All @@ -17,24 +18,22 @@ extension type SharedArrayBuffer._(JSObject _) implements JSObject {

external int get byteLength;

Int32List asInt32List() {
return _int32Array.callAsConstructor<JSInt32Array>(this).toDart;
SafeI32Array asInt32List() {
return SafeI32Array(_int32Array.callAsConstructor<JSInt32Array>(this));
}

ByteData asByteData(int offset, int length) {
return _dataView
.callAsConstructor<JSDataView>(this, offset.toJS, length.toJS)
.toDart;
SafeDataView asByteData(int offset, int length) {
return SafeDataView(_dataView.callAsConstructor<JSDataView>(
this, offset.toJS, length.toJS));
}

Uint8List asUint8List() {
return _uint8Array.callAsConstructor<JSUint8Array>(this).toDart;
SafeU8Array asUint8List() {
return SafeU8Array(_uint8Array.callAsConstructor<JSUint8Array>(this));
}

Uint8List asUint8ListSlice(int offset, int length) {
return _uint8Array
.callAsConstructor<JSUint8Array>(this, offset.toJS, length.toJS)
.toDart;
SafeU8Array asUint8ListSlice(int offset, int length) {
return SafeU8Array(_uint8Array.callAsConstructor<JSUint8Array>(
this, offset.toJS, length.toJS));
}
}

Expand Down Expand Up @@ -66,27 +65,27 @@ class Atomics {
return globalContext.has('Atomics');
}

static String wait(Int32List typedArray, int index, int value) {
return _Atomics.wait(typedArray.toJS, index, value).toDart;
static String wait(SafeI32Array typedArray, int index, int value) {
return _Atomics.wait(typedArray.inner, index, value).toDart;
}

static String waitWithTimeout(
Int32List typedArray, int index, int value, int timeOutInMillis) {
SafeI32Array typedArray, int index, int value, int timeOutInMillis) {
return _Atomics.waitWithTimeout(
typedArray.toJS, index, value, timeOutInMillis)
typedArray.inner, index, value, timeOutInMillis)
.toDart;
}

static void notify(Int32List typedArray, int index,
static void notify(SafeI32Array typedArray, int index,
[num count = double.infinity]) {
_Atomics.notify(typedArray.toJS, index, count);
_Atomics.notify(typedArray.inner, index, count);
}

static int store(Int32List typedArray, int index, int value) {
return _Atomics.store(typedArray.toJS, index, value);
static int store(SafeI32Array typedArray, int index, int value) {
return _Atomics.store(typedArray.inner, index, value);
}

static int load(Int32List typedArray, int index) {
return _Atomics.load(typedArray.toJS, index);
static int load(SafeI32Array typedArray, int index) {
return _Atomics.load(typedArray.inner, index);
}
}
14 changes: 7 additions & 7 deletions sqlite3/lib/src/wasm/js_interop/new_file_system_access.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';

import 'package:web/web.dart';

import 'core.dart';
import 'typed_data.dart';

@JS('navigator')
external Navigator get _navigator;
Expand All @@ -24,19 +24,19 @@ extension StorageManagerApi on StorageManager {
}

extension FileSystemSyncAccessHandleApi on FileSystemSyncAccessHandle {
int readDart(Uint8List buffer, [FileSystemReadWriteOptions? options]) {
int readDart(SafeU8Array buffer, [FileSystemReadWriteOptions? options]) {
if (options == null) {
return read(buffer.toJS);
return read(buffer);
} else {
return read(buffer.toJS, options);
return read(buffer, options);
}
}

int writeDart(Uint8List buffer, [FileSystemReadWriteOptions? options]) {
int writeDart(SafeU8Array buffer, [FileSystemReadWriteOptions? options]) {
if (options == null) {
return write(buffer.toJS);
return write(buffer);
} else {
return write(buffer.toJS, options);
return write(buffer, options);
}
}
}
Expand Down
88 changes: 77 additions & 11 deletions sqlite3/lib/src/wasm/js_interop/typed_data.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,87 @@
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';

import 'core.dart';

extension NativeUint8List on Uint8List {
/// A native version of [setRange] that takes another typed array directly.
/// This avoids the type checks part of [setRange] in compiled JavaScript
/// code.
void set(Uint8List from, int offset) {
toJS.callMethod('set'.toJS, from.toJS, offset.toJS);
// These types provide wrappers around JS typeddata objects that don't have
// the toDart extension on them.
// Using toDart is unsafe as it creates a reference in dart2js while copying in
// dart2wasm. This causes issues that are very hard to find, so we should be
// very explicit about where we copy and explain why that's safe.

extension type SafeBuffer(JSArrayBuffer inner) implements JSObject {
SafeU8Array asUint8Array([int offsetInBytes = 0, int? length]) {
return switch (length) {
null => SafeU8Array._bufferView(this, offsetInBytes),
var length =>
SafeU8Array._bufferViewWithLength(this, offsetInBytes, length),
};
}
}

extension NativeDataView on ByteData {
void setBigInt64(int offset, JsBigInt value, bool littleEndian) {
toJS.callMethod(
'setBigInt64'.toJS, offset.toJS, value.jsObject, littleEndian.toJS);
@JS('DataView')
extension type SafeDataView(JSDataView inner) implements JSObject {
@JS('')
external factory SafeDataView.entireBufferView(SafeBuffer buffer);

external void setInt32(int byteOffset, int value);
external int getInt32(int byteOffset);

external void setBigInt64(int offset, JsBigInt value, bool littleEndian);
}

@JS('Uint8Array')
extension type SafeU8Array(JSUint8Array inner) implements JSObject {
@JS('')
external factory SafeU8Array.allocate(int length);

@JS('')
external factory SafeU8Array._bufferView(SafeBuffer buffer, int byteOffset);

@JS('')
external factory SafeU8Array._bufferViewWithLength(
SafeBuffer buffer, int byteOffset, int length);

external void set(JSTypedArray array, int targetOffset);
external void fill(int value, int start, int end);

external SafeU8Array subarray(int begin, int end);

external int get length;

int operator [](int index) {
return getProperty<JSNumber>(index.toJS).toDartInt;
}

void operator []=(int index, int value) {
setProperty(index.toJS, value.toJS);
}

/// Implementation of [List.setRange] for wrapped u8 arrays.
void setRange(int start, int end, SafeU8Array other, [int skipCount = 0]) {
if (skipCount == 0 && end - start == other.length) {
set(other.inner, start);
} else {
set(other.subarray(skipCount, skipCount + (end - start)).inner, start);
}
}

/// Implementation of [List.setAll] for wrapped u8 arrays.
void setAll(int index, SafeU8Array other) {
set(other.inner, index);
}
}

@JS('Int32Array')
extension type SafeI32Array(JSInt32Array inner) implements JSObject {
@JS('')
external factory SafeI32Array.entireBufferView(SafeBuffer buffer);

int operator [](int index) {
return getProperty<JSNumber>(index.toJS).toDartInt;
}

void operator []=(int index, int value) {
setProperty(index.toJS, value.toJS);
}
}
3 changes: 2 additions & 1 deletion sqlite3/lib/src/wasm/js_interop/wasm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import 'core.dart';
import 'typed_data.dart';

import 'package:web/web.dart' as web;

Expand Down Expand Up @@ -71,7 +72,7 @@ extension type MemoryDescriptor._(JSObject _) implements JSObject {
extension type Memory._(JSObject _) implements JSObject {
external factory Memory(MemoryDescriptor descriptor);

external JSArrayBuffer get buffer;
external SafeBuffer get buffer;
}

@JS('WebAssembly.Global')
Expand Down
2 changes: 1 addition & 1 deletion sqlite3/lib/src/wasm/sqlite3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'dart:typed_data';
import 'package:web/web.dart' as web;

import '../implementation/sqlite3.dart';
import '../vfs.dart';
import 'vfs.dart';
import 'bindings.dart';
import 'js_interop.dart';
import 'wasm_interop.dart';
Expand Down
Loading
Loading