mirror of
https://github.com/fluencelabs/crossmodule
synced 2025-04-25 06:02:14 +00:00
init
This commit is contained in:
commit
7b8d142c7e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.idea/
|
||||||
|
build/
|
||||||
|
node_modules/
|
109
assembly/index.ts
Normal file
109
assembly/index.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
export class API {
|
||||||
|
|
||||||
|
invokeImpl: (ptr: i32, size: i32) => i32;
|
||||||
|
allocateImpl: (size: i32) => i32;
|
||||||
|
deallocateImpl: (ptr: i32, size: i32) => void;
|
||||||
|
storeImpl: (ptr: i32, byte: u8) => void;
|
||||||
|
loadImpl: (ptr: i32) => u8;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
invokeImpl: (ptr: i32, size: i32) => i32,
|
||||||
|
allocateImpl: (size: i32) => i32,
|
||||||
|
deallocateImpl: (ptr: i32, size: i32) => void,
|
||||||
|
storeImpl: (ptr: i32, byte: u8) => void,
|
||||||
|
loadImpl: (ptr: i32) => u8
|
||||||
|
) {
|
||||||
|
this.invokeImpl = invokeImpl;
|
||||||
|
this.allocateImpl = allocateImpl;
|
||||||
|
this.deallocateImpl = deallocateImpl;
|
||||||
|
this.storeImpl = storeImpl;
|
||||||
|
this.loadImpl = loadImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(ptr: i32, size: i32): i32 {
|
||||||
|
return this.invokeImpl(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
allocate(size: i32) :i32 {
|
||||||
|
return this.allocateImpl(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
deallocate(ptr: i32, size: i32): void {
|
||||||
|
this.deallocateImpl(size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
store(ptr: i32, byte: u8): void {
|
||||||
|
this.storeImpl(ptr, byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
load(ptr: i32): u8 {
|
||||||
|
return this.loadImpl(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ByteInvoke {
|
||||||
|
|
||||||
|
api: API;
|
||||||
|
|
||||||
|
constructor(api: API) {
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBytes(remotePtr: i32): Uint8Array {
|
||||||
|
let lenBytes: u8[] = new Array(4);
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
lenBytes[i] = this.api.load(remotePtr + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultLen: i32 = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
resultLen = resultLen | (lenBytes[i] << (8*(i - 4) as u8))
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultBytes = new Uint8Array(resultLen);
|
||||||
|
|
||||||
|
for (let i = 0; i < resultLen; i++) {
|
||||||
|
resultBytes[i] = this.api.load(remotePtr + i + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api.deallocate(remotePtr, resultLen + 4);
|
||||||
|
|
||||||
|
return resultBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBytes(localPtr: i32, len: i32): i32 {
|
||||||
|
let addr = this.api.allocate(len);
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
let b: u8 = load<u8>(localPtr + i) as u8;
|
||||||
|
this.api.store(addr + i, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(ptr: i32, len: i32): Uint8Array {
|
||||||
|
let requestPtr = this.sendBytes(ptr, len);
|
||||||
|
let resultPtr = this.api.invokeImpl(requestPtr, len);
|
||||||
|
return this.getBytes(resultPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StringInvoke {
|
||||||
|
|
||||||
|
byteInvoker: ByteInvoke;
|
||||||
|
|
||||||
|
constructor(api: API) {
|
||||||
|
this.byteInvoker = new ByteInvoke(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(request: string): string {
|
||||||
|
let utf8ptr = request.toUTF8();
|
||||||
|
let len = request.length;
|
||||||
|
|
||||||
|
let resultBytes = this.byteInvoker.invoke(utf8ptr, len);
|
||||||
|
|
||||||
|
return String.fromUTF8(resultBytes.buffer.data, resultBytes.length);
|
||||||
|
}
|
||||||
|
}
|
6
assembly/tsconfig.json
Normal file
6
assembly/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../node_modules/assemblyscript/std/assembly.json",
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
4687
package-lock.json
generated
Normal file
4687
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "crossmodule",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
|
||||||
|
"asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
|
||||||
|
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
|
||||||
|
"asbuild:test": "npm run asbuild:test:crossmodule",
|
||||||
|
"asbuild:test:crossmodule": "npx asc tests/assembly/crossmodule-test.spec.as.ts -b tests/build/crossmodule-test.wasm -t tests/build/crossmodule-test.wat --validate --sourceMap --importMemory --debug",
|
||||||
|
"test": "npm run asbuild:test && ava -v --serial",
|
||||||
|
"test:ci": "npm run asbuild:test && ava --fail-fast --serial"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^10.12.3",
|
||||||
|
"assemblyscript": "AssemblyScript/assemblyscript",
|
||||||
|
"ts-node": "^7.0.1",
|
||||||
|
"ava": "^1.4.0",
|
||||||
|
"typescript": "^3.1.6"
|
||||||
|
},
|
||||||
|
"ava": {
|
||||||
|
"compileEnhancements": true,
|
||||||
|
"extensions": [
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"require": [
|
||||||
|
"ts-node/register/transpile-only"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"tests/**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
63
tests/assembly/crossmodule-test.spec.as.ts
Normal file
63
tests/assembly/crossmodule-test.spec.as.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {API, StringInvoke} from "../../assembly/index";
|
||||||
|
import "allocator/arena";
|
||||||
|
|
||||||
|
declare function logStr(str: string): void;
|
||||||
|
|
||||||
|
class SelfAPI extends API {
|
||||||
|
|
||||||
|
static getApi(): API {
|
||||||
|
let invokeImpl = (ptr: i32, size: i32): i32 => {
|
||||||
|
let resultPtr = memory.allocate(size + 4);
|
||||||
|
|
||||||
|
let request = String.fromUTF8(ptr, size);
|
||||||
|
|
||||||
|
let strLen: i32 = request.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
let b: u8 = (strLen >> i * 8) as u8 & 0xFF;
|
||||||
|
store<u8>(resultPtr + i, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf8ptr = request.toUTF8();
|
||||||
|
let len = request.length;
|
||||||
|
|
||||||
|
for (let i = 4; i < len + 4; i++) {
|
||||||
|
store<u8>(resultPtr + i, load<u8>(utf8ptr + i - 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
logStr("size: " + size.toString());
|
||||||
|
|
||||||
|
|
||||||
|
return resultPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
let allocateImpl = (size: i32) :i32 => {
|
||||||
|
return memory.allocate(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
let deallocateImpl = (ptr: i32, size: i32): void => {
|
||||||
|
memory.free(ptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
let storeImpl = (ptr: i32, byte: u8): void => {
|
||||||
|
store<u8>(ptr, byte);
|
||||||
|
};
|
||||||
|
|
||||||
|
let loadImpl = (ptr: i32): u8 => {
|
||||||
|
return load<u8>(ptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
return new API(invokeImpl, allocateImpl, deallocateImpl, storeImpl, loadImpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CrossModuleTest {
|
||||||
|
static shouldReturnRightResult(): bool {
|
||||||
|
let stringInvoker = new StringInvoke(SelfAPI.getApi());
|
||||||
|
let request = "some request hello";
|
||||||
|
let response = stringInvoker.invoke(request);
|
||||||
|
|
||||||
|
assert(request == response, ":(");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
6
tests/assembly/tsconfig.json
Normal file
6
tests/assembly/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../node_modules/assemblyscript/std/assembly.json",
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts","*.ts"
|
||||||
|
]
|
||||||
|
}
|
3
tests/crossmodule.spec.ts
Normal file
3
tests/crossmodule.spec.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { defineTestsFromModule } from './utils/spec';
|
||||||
|
|
||||||
|
defineTestsFromModule('crossmodule-test');
|
113
tests/utils/helpers.ts
Normal file
113
tests/utils/helpers.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/// <reference path="../types/webassembly/index.d.ts" />
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as util from 'util';
|
||||||
|
|
||||||
|
import { demangle } from 'assemblyscript/lib/loader';
|
||||||
|
|
||||||
|
const DIGITALS_REGEXP = /([0-9]{1,})/g;
|
||||||
|
const UPPER_ALPHAS_REGEXP = /([A-Z]{1,})/g;
|
||||||
|
|
||||||
|
export type ImportEntries = { [key: string]: object };
|
||||||
|
export type ExportedEntry = { [key: string]: Function };
|
||||||
|
export type ExportedEntries = { [key: string]: ExportedEntry };
|
||||||
|
|
||||||
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
|
||||||
|
const F64 = new Float64Array(1);
|
||||||
|
const U64 = new Uint32Array(F64.buffer);
|
||||||
|
|
||||||
|
export function decamelize(str: string): string {
|
||||||
|
const t = str
|
||||||
|
.replace(DIGITALS_REGEXP, ' $1')
|
||||||
|
.replace(UPPER_ALPHAS_REGEXP, m => ' ' + (m.length === 1 ? m.toLowerCase() : m));
|
||||||
|
return t.charAt(0).toUpperCase() + t.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setup(testFileName: string): Promise<ExportedEntries> {
|
||||||
|
const pathName = path.resolve(__dirname, `../build/${ testFileName }.wasm`);
|
||||||
|
const file = await readFile(pathName, null);
|
||||||
|
if (!WebAssembly.validate(file)) {
|
||||||
|
throw new Error(`WebAssembly binary "${ pathName }" file not valid!`);
|
||||||
|
}
|
||||||
|
const imports = buildImports(`${ testFileName }.spec.as`, new WebAssembly.Memory({ initial: 2 }));
|
||||||
|
const result = await WebAssembly.instantiate(file, imports);
|
||||||
|
return demangle<ExportedEntries>(result.instance.exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpackToString64(value: number): string {
|
||||||
|
F64[0] = value;
|
||||||
|
return U64[1].toString(16) + U64[0].toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpackToString128(lo: number, hi: number): string {
|
||||||
|
return `0x${ (unpackToString64(hi) + unpackToString64(lo)).padStart(32, '0') }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getString(ptr: number, buffer: ArrayBuffer): string {
|
||||||
|
var U16 = new Uint16Array(buffer);
|
||||||
|
var U32 = new Uint32Array(buffer);
|
||||||
|
var dataLength = U32[ptr >>> 2];
|
||||||
|
var dataOffset = (ptr + 4) >>> 1;
|
||||||
|
var dataRemain = dataLength;
|
||||||
|
var parts = [];
|
||||||
|
const chunkSize = 1024;
|
||||||
|
while (dataRemain > chunkSize) {
|
||||||
|
let last = U16[dataOffset + chunkSize - 1];
|
||||||
|
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize;
|
||||||
|
let part = U16.subarray(dataOffset, dataOffset += size);
|
||||||
|
parts.push(String.fromCharCode.apply(String, part));
|
||||||
|
dataRemain -= size;
|
||||||
|
}
|
||||||
|
return parts.join('') + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildImports(name: string, memory: WebAssembly.Memory): ImportEntries {
|
||||||
|
const buffer = memory.buffer;
|
||||||
|
return {
|
||||||
|
env: {
|
||||||
|
memory,
|
||||||
|
abort(msgPtr: number, filePtr: number, line: number, column: number) {
|
||||||
|
if (msgPtr) {
|
||||||
|
throw new Error(
|
||||||
|
`Abort called by reason "${ getString(msgPtr, buffer) }" at ${ getString(filePtr, buffer) } [${ line }:${ column }]`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Abort called at ${ getString(filePtr, buffer) } [${ line }:${ column }]`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decoder: {
|
||||||
|
logStr(msgPtr: number) {
|
||||||
|
if (msgPtr) console.log(`[str]: ${ getString(msgPtr, buffer) }`);
|
||||||
|
},
|
||||||
|
logF64(value: number) {
|
||||||
|
console.log(`[f64]: ${ value }`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
encoder: {
|
||||||
|
logStr(msgPtr: number) {
|
||||||
|
if (msgPtr) console.log(`[str]: ${ getString(msgPtr, buffer) }`);
|
||||||
|
},
|
||||||
|
logF64(value: number) {
|
||||||
|
console.log(`[f64]: ${ value }`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[name]: {
|
||||||
|
logF64(value: number) {
|
||||||
|
console.log(`[f64]: ${ value }`);
|
||||||
|
},
|
||||||
|
logStr(msgPtr: number) {
|
||||||
|
if (msgPtr) console.log(`[str]: ${ getString(msgPtr, buffer) }`);
|
||||||
|
},
|
||||||
|
logU128Packed(msgPtr: number, lo: number, hi: number) {
|
||||||
|
if (msgPtr) {
|
||||||
|
console.log(`[u128] ${ getString(msgPtr, buffer) }: ${ unpackToString128(lo, hi) }`);
|
||||||
|
} else {
|
||||||
|
console.log(`[u128]: ${ unpackToString128(lo, hi) }`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
33
tests/utils/spec.ts
Normal file
33
tests/utils/spec.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import { setup, decamelize } from './helpers';
|
||||||
|
|
||||||
|
export async function defineTestsFromModule(moduleName: string) {
|
||||||
|
try {
|
||||||
|
const instance = await setup(moduleName);
|
||||||
|
|
||||||
|
for (const tests in instance) {
|
||||||
|
const testsInstance = instance[tests];
|
||||||
|
|
||||||
|
if (testsInstance.setUp) {
|
||||||
|
test.beforeEach(() => {
|
||||||
|
testsInstance.setUp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (testsInstance.tearDown) {
|
||||||
|
test.afterEach(() => {
|
||||||
|
testsInstance.tearDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const testName of Object.keys(testsInstance).filter(it => !(["setUp", "tearDown"].indexOf(it) != -1))) {
|
||||||
|
if (testName.startsWith("shouldAbort")) {
|
||||||
|
test(decamelize(testName), t => { t.throws(() => testsInstance[testName]()) });
|
||||||
|
} else {
|
||||||
|
test(decamelize(testName), t => t.truthy(testsInstance[testName]()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error loading WebAssembly module:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user