Skip to content

Commit

Permalink
* new memory accessors setI52, setI64Big, getI52, getI64Big
Browse files Browse the repository at this point in the history
* removed support for long form automatic marshaler. It has impact to mono_bind_static_method
  • Loading branch information
pavelsavara committed May 13, 2022
1 parent 3749f82 commit d119e50
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ItemGroup>
<Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\DataViewTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\MemoryTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\TypedArrayTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\ArrayTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using Xunit;

namespace System.Runtime.InteropServices.JavaScript.Tests
{
public class MemoryTests
{
[Theory]
[InlineData(-1L)]
[InlineData(-42L)]
[InlineData(int.MinValue)]
[InlineData(-9007199254740991L)]//MIN_SAFE_INTEGER
[InlineData(1L)]
[InlineData(0L)]
[InlineData(42L)]
[InlineData(int.MaxValue)]
[InlineData(0xF_FFFF_FFFFL)]
[InlineData(9007199254740991L)]//MAX_SAFE_INTEGER
public static unsafe void Int52TestOK(long value)
{
long expected = value;
long actual2 = value;
var bagFn = new Function("ptr", "ptr2", @"
const value=globalThis.App.MONO.getI52(ptr);
globalThis.App.MONO.setI52(ptr2, value);
return value;");

uint ptr = (uint)Unsafe.AsPointer(ref expected);
uint ptr2 = (uint)Unsafe.AsPointer(ref actual2);

object o = bagFn.Call(null, ptr, ptr2);
if (value < int.MaxValue && value > int.MinValue)
{
Assert.IsType<int>(o);
long actual = (int)o;
Assert.Equal(expected, actual);
}
Assert.Equal(expected, actual2);
}
}
}
3 changes: 2 additions & 1 deletion src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ const DotnetSupportLib = {
// we replace implementation of readAsync and fetch
// replacement of require is there for consistency with ES6 code
$DOTNET__postset: `
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require};
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews};
let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:Promise.resolve(require)},
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module },
__dotnet_replacements);
updateGlobalBufferAndViews = __dotnet_replacements.updateGlobalBufferAndViews;
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
require = __dotnet_replacements.requireOut;
Expand Down
18 changes: 14 additions & 4 deletions src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,11 @@ declare function setU32(offset: _MemOffset, value: _NumberOrPointer): void;
declare function setI8(offset: _MemOffset, value: number): void;
declare function setI16(offset: _MemOffset, value: number): void;
declare function setI32(offset: _MemOffset, value: _NumberOrPointer): void;
declare function setI64(offset: _MemOffset, value: number): void;
/**
* Throws for values which are not integer. See Number.isInteger()
*/
declare function setI52(offset: _MemOffset, value: number): void;
declare function setI64Big(offset: _MemOffset, value: bigint): void;
declare function setF32(offset: _MemOffset, value: number): void;
declare function setF64(offset: _MemOffset, value: number): void;
declare function getU8(offset: _MemOffset): number;
Expand All @@ -301,7 +305,11 @@ declare function getU32(offset: _MemOffset): number;
declare function getI8(offset: _MemOffset): number;
declare function getI16(offset: _MemOffset): number;
declare function getI32(offset: _MemOffset): number;
declare function getI64(offset: _MemOffset): number;
/**
* Throws for Number.MIN_SAFE_INTEGER < value < Number.MAX_SAFE_INTEGER
*/
declare function getI52(offset: _MemOffset): number;
declare function getI64Big(offset: _MemOffset): bigint;
declare function getF32(offset: _MemOffset): number;
declare function getF64(offset: _MemOffset): number;

Expand Down Expand Up @@ -329,7 +337,8 @@ declare const MONO: {
setI8: typeof setI8;
setI16: typeof setI16;
setI32: typeof setI32;
setI64: typeof setI64;
setI52: typeof setI52;
setI64Big: typeof setI64Big;
setU8: typeof setU8;
setU16: typeof setU16;
setU32: typeof setU32;
Expand All @@ -338,7 +347,8 @@ declare const MONO: {
getI8: typeof getI8;
getI16: typeof getI16;
getI32: typeof getI32;
getI64: typeof getI64;
getI52: typeof getI52;
getI64Big: typeof getI64Big;
getU8: typeof getU8;
getU16: typeof getU16;
getU32: typeof getU32;
Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/runtime/es6/dotnet.es6.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DotnetSupportLib = {
// Emscripten's getBinaryPromise is not async for NodeJs, but we would like to have it async, so we replace it.
// We also replace implementation of readAsync and fetch
$DOTNET__postset: `
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require};
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews};
if (ENVIRONMENT_IS_NODE) {
__dotnet_replacements.requirePromise = import('module').then(mod => {
const require = mod.createRequire(import.meta.url);
Expand Down Expand Up @@ -52,6 +52,7 @@ let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:__dotnet_replacements.requirePromise },
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module },
__dotnet_replacements);
updateGlobalBufferAndViews = __dotnet_replacements.updateGlobalBufferAndViews;
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
require = __dotnet_replacements.requireOut;
Expand Down
19 changes: 13 additions & 6 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ import { mono_wasm_release_cs_owned_object } from "./gc-handles";
import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket";
import cwraps from "./cwraps";
import {
setI8, setI16, setI32, setI64,
setI8, setI16, setI32, setI52,
setU8, setU16, setU32, setF32, setF64,
getI8, getI16, getI32, getI64,
getU8, getU16, getU32, getF32, getF64,
getI8, getI16, getI32, getI52,
getU8, getU16, getU32, getF32, getF64, afterUpdateGlobalBufferAndViews, getI64Big, setI64Big,
} from "./memory";
import { create_weak_ref } from "./weak-ref";
import { fetch_like, readAsync_like } from "./polyfills";
Expand Down Expand Up @@ -96,7 +96,8 @@ const MONO = {
setI8,
setI16,
setI32,
setI64,
setI52,
setI64Big,
setU8,
setU16,
setU32,
Expand All @@ -105,7 +106,8 @@ const MONO = {
getI8,
getI16,
getI32,
getI64,
getI52,
getI64Big,
getU8,
getU16,
getU32,
Expand Down Expand Up @@ -176,7 +178,7 @@ let exportedAPI: DotnetPublicAPI;
function initializeImportsAndExports(
imports: { isESM: boolean, isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function, ExitStatus: ExitStatusError, requirePromise: Promise<Function> },
exports: { mono: any, binding: any, internal: any, module: any },
replacements: { fetch: any, readAsync: any, require: any, requireOut: any, noExitRuntime: boolean },
replacements: { fetch: any, readAsync: any, require: any, requireOut: any, noExitRuntime: boolean, updateGlobalBufferAndViews: Function },
): DotnetPublicAPI {
const module = exports.module as DotnetModule;
const globalThisAny = globalThis as any;
Expand Down Expand Up @@ -233,6 +235,11 @@ function initializeImportsAndExports(
replacements.fetch = runtimeHelpers.fetch;
replacements.readAsync = readAsync_like;
replacements.requireOut = module.imports.require;
const originalUpdateGlobalBufferAndViews = replacements.updateGlobalBufferAndViews;
replacements.updateGlobalBufferAndViews = (buffer: Buffer) => {
originalUpdateGlobalBufferAndViews(buffer);
afterUpdateGlobalBufferAndViews(buffer);
};

replacements.noExitRuntime = ENVIRONMENT_IS_WEB;

Expand Down
70 changes: 62 additions & 8 deletions src/mono/wasm/runtime/memory.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Module } from "./imports";
import { assert } from "./types";
import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten";

const alloca_stack: Array<VoidPtr> = [];
const alloca_buffer_size = 32 * 1024;
let alloca_base: VoidPtr, alloca_offset: VoidPtr, alloca_limit: VoidPtr;
let HEAPI64: BigInt64Array = <any>null;

function _ensure_allocated(): void {
if (alloca_base)
Expand All @@ -13,6 +15,10 @@ function _ensure_allocated(): void {
alloca_limit = <VoidPtr>(<any>alloca_base + alloca_buffer_size);
}

function is_bingint_supported() {
return typeof BigInt !== "undefined" && typeof BigInt64Array !== "undefined";
}

export function temp_malloc(size: number): VoidPtr {
_ensure_allocated();
if (!alloca_stack.length)
Expand Down Expand Up @@ -48,7 +54,7 @@ export function setU16(offset: _MemOffset, value: number): void {
Module.HEAPU16[<any>offset >>> 1] = value;
}

export function setU32 (offset: _MemOffset, value: _NumberOrPointer) : void {
export function setU32(offset: _MemOffset, value: _NumberOrPointer): void {
Module.HEAPU32[<any>offset >>> 2] = <number><any>value;
}

Expand All @@ -60,13 +66,34 @@ export function setI16(offset: _MemOffset, value: number): void {
Module.HEAP16[<any>offset >>> 1] = value;
}

export function setI32 (offset: _MemOffset, value: _NumberOrPointer) : void {
export function setI32(offset: _MemOffset, value: _NumberOrPointer): void {
Module.HEAP32[<any>offset >>> 2] = <number><any>value;
}

// NOTE: Accepts a number, not a BigInt, so values over Number.MAX_SAFE_INTEGER will be corrupted
export function setI64(offset: _MemOffset, value: number): void {
Module.setValue(<VoidPtr><any>offset, value, "i64");
/**
* Throws for values which are not integer. See Number.isInteger()
*/
export function setI52(offset: _MemOffset, value: number): void {
// 52 bits = 0x1F_FFFF_FFFF_FFFF
assert(Number.isSafeInteger(value), "Int64 value out of JavaScript Number safe integer range");
let hi: number;
let lo: number;
if (value < 0) {
value = -1 - value;
hi = 0x8000_0000 + ((value >>> 32) ^ 0x001F_FFFF);
lo = (value & 0xFFFF_FFFF) ^ 0xFFFF_FFFF;
}
else {
hi = value >>> 32;
lo = value & 0xFFFF_FFFF;
}
Module.HEAPU32[1 + <any>offset >>> 2] = hi;
Module.HEAPU32[<any>offset >>> 2] = lo;
}

export function setI64Big(offset: _MemOffset, value: bigint): void {
assert(is_bingint_supported(), "BigInt is not supported.");
HEAPI64[<any>offset >>> 3] = value;
}

export function setF32(offset: _MemOffset, value: number): void {
Expand Down Expand Up @@ -102,9 +129,30 @@ export function getI32(offset: _MemOffset): number {
return Module.HEAP32[<any>offset >>> 2];
}

// NOTE: Returns a number, not a BigInt. This means values over Number.MAX_SAFE_INTEGER will be corrupted
export function getI64(offset: _MemOffset): number {
return Module.getValue(<number><any>offset, "i64");
/**
* Throws for Number.MIN_SAFE_INTEGER < value < Number.MAX_SAFE_INTEGER
*/
export function getI52(offset: _MemOffset): number {
// 52 bits = 0x1F_FFFF_FFFF_FFFF
const hi = Module.HEAPU32[1 + (<any>offset >>> 2)];
const lo = Module.HEAPU32[<any>offset >>> 2];
const sign = hi & 0x8000_0000;
const exp = hi & 0x7FE0_0000;
if (sign) {
assert(exp === 0x7FE0_0000, "Int64 value out of JavaScript Number safe integer range");
const nhi = (hi & 0x000F_FFFF) ^ 0x000F_FFFF;
const nlo = lo ^ 0xFFFF_FFFF;
return -1 - ((nhi * 0x1_0000_0000) + nlo);
}
else {
assert(exp === 0, "Int64 value out of JavaScript Number safe integer range");
return (hi * 0x1_0000_0000) + lo;
}
}

export function getI64Big(offset: _MemOffset): bigint {
assert(is_bingint_supported(), "BigInt is not supported.");
return HEAPI64[<any>offset >>> 3];
}

export function getF32(offset: _MemOffset): number {
Expand All @@ -114,3 +162,9 @@ export function getF32(offset: _MemOffset): number {
export function getF64(offset: _MemOffset): number {
return Module.HEAPF64[<any>offset >>> 3];
}

export function afterUpdateGlobalBufferAndViews(buffer: Buffer): void {
if (is_bingint_supported()) {
HEAPI64 = new BigInt64Array(buffer);
}
}
13 changes: 5 additions & 8 deletions src/mono/wasm/runtime/method-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js";
import {
_create_temp_frame,
getI32, getU32, getF32, getF64,
setI32, setU32, setF32, setF64, setI64,
setI32, setU32, setF32, setF64
} from "./memory";
import {
_get_args_root_buffer_for_method_call, _get_buffer_for_method_call,
Expand Down Expand Up @@ -131,7 +131,7 @@ export function _create_primitive_converters(): void {
result.set("j", { steps: [{ convert: js_to_mono_enum.bind(BINDING), indirect: "i32" }], size: 8 });

result.set("i", { steps: [{ indirect: "i32" }], size: 8 });
result.set("l", { steps: [{ indirect: "i64" }], size: 8 });
// result.set("l", { steps: [{ indirect: "i64" }], size: 8 });
result.set("f", { steps: [{ indirect: "float" }], size: 8 });
result.set("d", { steps: [{ indirect: "double" }], size: 8 });
}
Expand Down Expand Up @@ -218,7 +218,6 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args
setU32,
setF32,
setF64,
setI64,
scratchValueRoot: converter.scratchValueRoot
};
let indirectLocalOffset = 0;
Expand Down Expand Up @@ -288,8 +287,6 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args
case "double":
body.push(`setF64(${offsetText}, ${valueKey});`);
break;
case "i64":
body.push(`setI64(${offsetText}, ${valueKey});`);
break;
default:
throw new Error("Unimplemented indirect type: " + step.indirect);
Expand Down Expand Up @@ -564,8 +561,8 @@ We currently don't use these types because it makes typeScript compiler very slo
declare const enum ArgsMarshal {
Int32 = "i", // int32
Int32Enum = "j", // int32 - Enum with underlying type of int32
Int64 = "l", // int64
Int64Enum = "k", // int64 - Enum with underlying type of int64
// Int64 = "l", // int64
// Int64Enum = "k", // int64 - Enum with underlying type of int64
Float32 = "f", // float
Float64 = "d", // double
String = "s", // string
Expand All @@ -584,7 +581,7 @@ export type ArgsMarshalString = ""
| `${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${_ExtraArgsMarshalOperators}`;
*/

type ConverterStepIndirects = "u32" | "i32" | "float" | "double" | "i64" | "reference"
type ConverterStepIndirects = "u32" | "i32" | "float" | "double" | "reference"

export type Converter = {
steps: {
Expand Down

0 comments on commit d119e50

Please sign in to comment.