├── .npmignore ├── .gitignore ├── src ├── std │ ├── collection │ │ └── Map.ts │ ├── nullptrof.ts │ ├── addressof.ts │ ├── function │ │ ├── isPointer.ts │ │ ├── isCStruct.ts │ │ ├── getUniqueCounter32.ts │ │ ├── getUniqueCounter64.ts │ │ └── getRandomValues.ts │ ├── sizeof.ts │ ├── offsetof.ts │ ├── unmake.ts │ ├── make.ts │ ├── mapStruct.ts │ └── string.ts ├── polyfill │ ├── index.ts │ └── atomic.ts ├── transformer │ ├── __test__ │ │ ├── defined.ts │ │ ├── clear.js │ │ ├── case │ │ │ ├── asm.test.ts │ │ │ ├── mergeOperator.test.ts │ │ │ ├── snippet.ts │ │ │ ├── defined.test.ts │ │ │ ├── assert.test.ts │ │ │ ├── bigint.test.ts │ │ │ ├── reinterpretCast.test.ts │ │ │ ├── parameter.test.ts │ │ │ ├── memory.test.ts │ │ │ ├── make.test.ts │ │ │ ├── identifier.test.ts │ │ │ ├── offsetof.test.ts │ │ │ ├── deasync.test.ts │ │ │ ├── makeSharedPtr.test.ts │ │ │ └── conditionCompile.test.ts │ │ ├── compare.ts │ │ └── transformer.ts │ ├── error.ts │ ├── function │ │ ├── formatIdentifier.ts │ │ ├── getDirname.ts │ │ ├── getFilePath.ts │ │ ├── relativePath.ts │ │ ├── getStructMeta.ts │ │ ├── pushDeclaration.ts │ │ ├── getImportIdentifier.ts │ │ ├── isMergeOperator.ts │ │ ├── pushImport.ts │ │ ├── compute.ts │ │ ├── mergeOperator2Operator.ts │ │ ├── reportError.ts │ │ └── parseImports.ts │ ├── visitor │ │ ├── functionDeclarationVisitor.ts │ │ ├── bigIntLiteralVisitor.ts │ │ ├── taggedTemplateExpressionVisitor.ts │ │ ├── bindingElementVisitor.ts │ │ ├── parameterVisitor.ts │ │ ├── function │ │ │ ├── addImportStatements.ts │ │ │ └── processAsm.ts │ │ ├── ifStatementVisitor.ts │ │ ├── decoratorVisitor.ts │ │ ├── propertyAssignmentVisitor.ts │ │ ├── propertyDeclarationVisitor.ts │ │ ├── variableDeclarationVisitor.ts │ │ ├── blockVisitor.ts │ │ ├── expressionStatementVisitor.ts │ │ ├── expressionVisitor.ts │ │ ├── conditionalExpressionVisitor.ts │ │ ├── classDeclarationVisitor.ts │ │ └── identifierVisitor.ts │ ├── type.ts │ ├── defined.ts │ └── constant.ts ├── function │ ├── unimplemented.ts │ └── definedMetaProperty.ts ├── thread │ ├── sync.ts │ ├── runThread.ts │ ├── barrier.ts │ ├── initFunction.ts │ ├── cond.ts │ ├── initModule.ts │ ├── initClass.ts │ ├── semaphore.ts │ ├── mutex.ts │ └── atomics.ts ├── symbol.ts ├── stack.ts ├── internal.ts ├── webassembly │ ├── thread.ts │ ├── runtime │ │ ├── semaphore.ts │ │ └── asm │ │ │ ├── libc.asm.ts │ │ │ ├── libc64.asm.ts │ │ │ ├── libc.ts │ │ │ ├── thread.ts │ │ │ ├── thread.asm.ts │ │ │ └── thread64.asm.ts │ ├── threadEntry.js │ └── runThread.ts ├── config.ts ├── asm │ ├── ASMRunner.ts │ ├── memory.asm.ts │ ├── memory64.asm.ts │ └── memory.ts ├── allocator │ ├── Allocator.ts │ └── Table.ts ├── staticData.ts ├── ctypeEnumRead.ts ├── ctypeEnumWrite.ts ├── index.ts ├── typedef.ts └── error.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.mjs.json ├── scripts └── check-publish-status.js ├── .vscode └── launch.json ├── @types └── index.d.ts ├── tsconfig.build.json ├── include ├── wasmsemaphore.h ├── wasmenv.h └── wasmpthread.h ├── jest.config.js ├── LICENSE ├── tsconfig.json ├── rollup.config.js └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | build/package.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_store 3 | dist 4 | !build/webpack/loader/**/dist -------------------------------------------------------------------------------- /src/std/collection/Map.ts: -------------------------------------------------------------------------------- 1 | 2 | @struct 3 | export default class Map { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/polyfill/index.ts: -------------------------------------------------------------------------------- 1 | import atomic from './atomic' 2 | import bigint from './bigint' 3 | 4 | atomic() 5 | bigint() 6 | -------------------------------------------------------------------------------- /src/transformer/__test__/defined.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | global.defined = () => false 3 | // @ts-ignore 4 | global.WASM_64 = false 5 | -------------------------------------------------------------------------------- /src/transformer/error.ts: -------------------------------------------------------------------------------- 1 | export const TYPE_MISMATCH = 10000 2 | 3 | export const INVALID_OPERATE = 20000 4 | 5 | export const SYNTAX_ERROR = 30000 6 | -------------------------------------------------------------------------------- /src/function/unimplemented.ts: -------------------------------------------------------------------------------- 1 | export default function unimplemented(pointer: pointer, value?: any, size?: any): never { 2 | throw new Error('unimplemented') 3 | } 4 | -------------------------------------------------------------------------------- /src/transformer/function/formatIdentifier.ts: -------------------------------------------------------------------------------- 1 | export default function formatIdentifier(identifier: string, index: number) { 2 | return `__cheap__${identifier}__${index}` 3 | } 4 | -------------------------------------------------------------------------------- /src/std/nullptrof.ts: -------------------------------------------------------------------------------- 1 | import { symbolStructAddress } from '../symbol' 2 | 3 | export default function nullptrof(struct: T): void { 4 | struct[symbolStructAddress] = nullptr 5 | } 6 | -------------------------------------------------------------------------------- /src/std/addressof.ts: -------------------------------------------------------------------------------- 1 | import { symbolStructAddress } from '../symbol' 2 | 3 | export default function addressof(struct: T): pointer { 4 | return struct[symbolStructAddress] as pointer 5 | } 6 | -------------------------------------------------------------------------------- /src/std/function/isPointer.ts: -------------------------------------------------------------------------------- 1 | import { is } from '@libmedia/common' 2 | 3 | export default function isPointer(p: any): p is pointer { 4 | return (defined(WASM_64) ? is.bigint(p) : is.number(p)) && p >= nullptr 5 | } 6 | -------------------------------------------------------------------------------- /src/function/definedMetaProperty.ts: -------------------------------------------------------------------------------- 1 | export default function definedMetaProperty(target: any, key: symbol, value: any) { 2 | Object.defineProperty(target, key, { 3 | value, 4 | writable: false, 5 | enumerable: false, 6 | configurable: false 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/transformer/function/getDirname.ts: -------------------------------------------------------------------------------- 1 | import { dirname as pathDirname } from 'path' 2 | import { fileURLToPath } from 'url' 3 | 4 | export default function getDirname(meta: string) { 5 | if (typeof __dirname !== 'undefined') { 6 | // CJS 环境 7 | return __dirname 8 | } 9 | return pathDirname(fileURLToPath(meta)) 10 | } 11 | -------------------------------------------------------------------------------- /src/std/function/isCStruct.ts: -------------------------------------------------------------------------------- 1 | import { symbolStructAddress } from '../../symbol' 2 | import { object } from '@libmedia/common' 3 | 4 | /** 5 | * 判断是否是 CStruct 6 | * 7 | * @param struct 8 | * @returns 9 | */ 10 | export default function isCStruct(struct: Object) { 11 | return object.has(struct, symbolStructAddress as any) 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/cjs", 5 | "declaration": false, 6 | "module": "CommonJS" 7 | }, 8 | "exclude": [ 9 | "*.test.ts", 10 | "__test__", 11 | "/node_modules/**", 12 | "**/dist", 13 | "**/transformer/**", 14 | "**/polyfill/**" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/transformer/function/getFilePath.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | 3 | export default function getFilePath(program: ts.Program, current: string, target: string) { 4 | const path = ts.resolveModuleName( 5 | target, 6 | current, 7 | program.getCompilerOptions(), 8 | ts.sys 9 | ) 10 | return path.resolvedModule?.resolvedFileName 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/esm", 5 | "module": "ESNext", 6 | "moduleResolution": "node" 7 | }, 8 | "exclude": [ 9 | "*.test.ts", 10 | "__test__", 11 | "/node_modules/**", 12 | "**/dist", 13 | "**/transformer/**", 14 | "**/polyfill/**" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/std/function/getUniqueCounter32.ts: -------------------------------------------------------------------------------- 1 | 2 | import { uniqueCounter32 } from '../../staticData' 3 | import * as atomics from '../../thread/atomics' 4 | 5 | /** 6 | * 获取全局唯一的 uint32 计数器,在页面的生命周期内返回的值是唯一的 7 | * 8 | * 线程安全但需要注意回环 9 | * 10 | * @returns 11 | */ 12 | export default function getUniqueCounter32() { 13 | return atomics.add(uniqueCounter32, 1) 14 | } 15 | -------------------------------------------------------------------------------- /src/std/function/getUniqueCounter64.ts: -------------------------------------------------------------------------------- 1 | 2 | import { uniqueCounter64 } from '../../staticData' 3 | import * as atomics from '../../thread/atomics' 4 | 5 | /** 6 | * 获取全局唯一的 uint64 计数器,在页面的生命周期内返回的值是唯一的 7 | * 8 | * 线程安全但需要注意回环 9 | * 10 | * @returns 11 | */ 12 | export default function getUniqueCounter64() { 13 | return atomics.add(uniqueCounter64, 1n) 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.mjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/mjs", 5 | "declaration": false, 6 | "module": "ESNext", 7 | "moduleResolution": "node" 8 | }, 9 | "exclude": [ 10 | "*.test.ts", 11 | "__test__", 12 | "/node_modules/**", 13 | "**/dist", 14 | "**/transformer/**", 15 | "**/polyfill/**" 16 | ] 17 | } -------------------------------------------------------------------------------- /src/transformer/function/relativePath.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | export default function relativePath(a: string, b: string) { 4 | let p = path.relative(path.dirname(a), b) 5 | 6 | // 去掉 d.ts .ts .js 后缀 7 | p = p.replace(/(\.d)?\.[t|j]s$/, '') 8 | 9 | if (path.isAbsolute(p)) { 10 | return p 11 | } 12 | else if (/\.\.\//.test(p)) { 13 | return p 14 | } 15 | return './' + p 16 | } 17 | -------------------------------------------------------------------------------- /src/transformer/function/getStructMeta.ts: -------------------------------------------------------------------------------- 1 | import type { Struct } from '../struct' 2 | 3 | export default function getStructMeta(struct: Struct, key: string) { 4 | let meta = struct.meta.get(key) 5 | if (meta) { 6 | return meta 7 | } 8 | let next = struct.parent 9 | while (next) { 10 | meta = next.meta.get(key) 11 | if (meta) { 12 | return meta 13 | } 14 | next = next.parent 15 | } 16 | 17 | return null 18 | } 19 | -------------------------------------------------------------------------------- /src/transformer/function/pushDeclaration.ts: -------------------------------------------------------------------------------- 1 | import type { DeclarationData } from '../type' 2 | 3 | export default function pushDeclaration(keys: DeclarationData[], name: string, formatName: string) { 4 | for (let i = 0; i < keys.length; i++) { 5 | if (keys[i].name === name) { 6 | return keys[i] 7 | } 8 | } 9 | 10 | const item = { 11 | name, 12 | formatName 13 | } 14 | 15 | keys.push(item) 16 | 17 | return item 18 | } 19 | -------------------------------------------------------------------------------- /src/thread/sync.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 同一个线程内异步方法串行执行 3 | */ 4 | 5 | export class Sync { 6 | list: (() => void)[] = [] 7 | } 8 | 9 | export async function lock(sync: Sync) { 10 | return new Promise((resolve) => { 11 | sync.list.push(resolve) 12 | if (sync.list.length === 1) { 13 | resolve() 14 | } 15 | }) 16 | } 17 | 18 | export function unlock(sync: Sync) { 19 | sync.list.shift() 20 | if (sync.list.length) { 21 | sync.list[0]() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/transformer/visitor/functionDeclarationVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | 5 | export default function (node: ts.FunctionDeclaration, visitor: ts.Visitor): ts.Node | ts.Node[] { 6 | 7 | if (node.name) { 8 | statement.addFunc(node.name.escapedText as string, node) 9 | } 10 | 11 | statement.pushStack() 12 | 13 | let newNode = ts.visitEachChild(node, visitor, statement.context) 14 | 15 | statement.popStack() 16 | 17 | return newNode 18 | } 19 | -------------------------------------------------------------------------------- /src/symbol.ts: -------------------------------------------------------------------------------- 1 | export const symbolStruct = Symbol('Struct') 2 | export const symbolStructLength = Symbol('StructLength') 3 | export const symbolStructMaxBaseTypeByteLength = Symbol('StructMaxBaseTypeByteLength') 4 | export const symbolStructAddress = Symbol('StructAddress') 5 | export const symbolStructKeysQueue = Symbol('StructKeysQueue') 6 | export const symbolStructKeysMeta = Symbol('StructKeysMeta') 7 | export const symbolStructKeysInstance = Symbol('StructKeysInstance') 8 | export const symbolStructProxyRevoke = Symbol('StructProxyRevoke') 9 | -------------------------------------------------------------------------------- /src/std/sizeof.ts: -------------------------------------------------------------------------------- 1 | import { symbolStruct, symbolStructLength } from '../symbol' 2 | import { is } from '@libmedia/common' 3 | import type { CTypeEnum, Struct } from '../typedef' 4 | import { CTypeEnum2Bytes } from '../typedef' 5 | 6 | export default function sizeof(type: CTypeEnum | Struct): size { 7 | if (is.number(type)) { 8 | return reinterpret_cast((CTypeEnum2Bytes[type] || 0) as uint32) 9 | } 10 | else if (is.func(type) && type.prototype[symbolStruct]) { 11 | return reinterpret_cast(type.prototype[symbolStructLength] as uint32) 12 | } 13 | return 0 14 | } 15 | -------------------------------------------------------------------------------- /src/stack.ts: -------------------------------------------------------------------------------- 1 | import { StackPointer, StackTop, StackSize } from './heap' 2 | 3 | export function malloc(size: size): pointer { 4 | assert(reinterpret_cast>(StackPointer.value) - size >= StackTop, 'stack up overflow') 5 | StackPointer.value = (StackPointer.value as size) - size 6 | return StackPointer.value 7 | } 8 | 9 | export function free(size: size): void { 10 | assert(reinterpret_cast>(StackPointer.value) + size <= StackTop + reinterpret_cast(StackSize), 'stack down overflow') 11 | StackPointer.value = (StackPointer.value as size) + size 12 | } 13 | -------------------------------------------------------------------------------- /src/std/offsetof.ts: -------------------------------------------------------------------------------- 1 | import { symbolStructKeysMeta } from '../symbol' 2 | import type { KeyMeta } from '../typedef' 3 | import { KeyMetaKey } from '../typedef' 4 | 5 | export default function offsetof) => any>( 7 | struct: T, 8 | key: T extends new (init?: Partial<{}>) => infer U ? keyof U : never 9 | ): uint32 { 10 | const meta = struct[symbolStructKeysMeta] as Map 11 | if (meta.has(key as string)) { 12 | return meta.get(key as string)[KeyMetaKey.BaseAddressOffset] 13 | } 14 | throw new Error(`not found key: ${key as string}`) 15 | } 16 | -------------------------------------------------------------------------------- /src/transformer/visitor/bigIntLiteralVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import type ts from 'typescript' 3 | import statement from '../statement' 4 | 5 | export default function (node: ts.BigIntLiteral, visitor: ts.Visitor): ts.Node | ts.Node[] { 6 | if (statement.cheapCompilerOptions.defined.BIGINT_LITERAL === false) { 7 | return statement.context.factory.createCallExpression( 8 | statement.context.factory.createIdentifier('BigInt'), 9 | undefined, 10 | [ 11 | statement.context.factory.createNumericLiteral(node.text.replace(/n$/, '')) 12 | ] 13 | ) 14 | } 15 | return node 16 | } 17 | -------------------------------------------------------------------------------- /scripts/check-publish-status.js: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from 'path' 2 | import { readFileSync } from 'fs' 3 | import { fileURLToPath } from 'url' 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)) 6 | 7 | const json = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8')) 8 | 9 | if (json.dependencies) { 10 | const keys = Object.keys(json.dependencies) 11 | for (let i = 0; i < keys.length; i++) { 12 | if (/^workspace:\*$/.test(json.dependencies[keys[i]])) { 13 | console.error(`dependencies ${keys[i]} not set real version`) 14 | process.exit(1) 15 | } 16 | } 17 | } 18 | process.exit(0) 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Jest Debug", 11 | "program": "${workspaceRoot}/node_modules/jest/bin/jest", 12 | "stopOnEntry": false, 13 | "args": ["--runInBand", "--env=node", "${fileBasename}"], 14 | "runtimeArgs": [ 15 | "--inspect-brk" 16 | ], 17 | "cwd": "${workspaceRoot}", 18 | "sourceMaps": true, 19 | "console": "integratedTerminal" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/internal.ts: -------------------------------------------------------------------------------- 1 | export { 2 | symbolStruct, 3 | symbolStructAddress, 4 | symbolStructKeysInstance, 5 | symbolStructKeysMeta, 6 | symbolStructKeysQueue, 7 | symbolStructLength, 8 | symbolStructMaxBaseTypeByteLength, 9 | symbolStructProxyRevoke 10 | } from './symbol' 11 | 12 | export { CTypeEnumRead } from './ctypeEnumRead' 13 | export { CTypeEnumWrite } from './ctypeEnumWrite' 14 | export { makeSharedPtr } from './std/smartPtr/SharedPtr' 15 | 16 | export { default as definedMetaProperty } from './function/definedMetaProperty' 17 | 18 | export { 19 | Allocator, 20 | initThread, 21 | getHeap, 22 | getHeapU8, 23 | getView 24 | } from './heap' 25 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module 'cheap-worker-loader!*' { 3 | const content: new (...args: any[]) => any 4 | export default content 5 | } 6 | 7 | declare module '*.asm' { 8 | const content: string 9 | export default content 10 | } 11 | 12 | declare const ENV_NODE: boolean 13 | 14 | declare const DEBUG: boolean 15 | 16 | declare const ENABLE_THREADS: boolean 17 | 18 | declare const CHEAP_HEAP_INITIAL: number 19 | 20 | declare const ENV_WEBPACK: boolean 21 | 22 | declare const WASM_64: boolean 23 | 24 | declare const ENABLE_THREADS_SPLIT: boolean 25 | 26 | declare const ENV_CSP: boolean 27 | 28 | declare const ENV_CJS: boolean 29 | 30 | declare const USE_WORKER_SELF_URL: boolean 31 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "target": "es2015", 6 | "paths": { 7 | "@libmedia/common": ["../common/src"], 8 | "@libmedia/common/io": ["../common/src/io"] 9 | }, 10 | "typeRoots": [ 11 | "./node_modules/@types", 12 | ], 13 | "types": [ 14 | "node", 15 | "jest" 16 | ], 17 | "lib": [ 18 | "dom", 19 | "ESNext" 20 | ] 21 | }, 22 | "include": [ 23 | "./src/transformer/index.ts", 24 | "./src/webassembly/wasm-opt.ts" 25 | ], 26 | "exclude": [ 27 | "*.test.ts", 28 | "__test__", 29 | "/node_modules/**", 30 | "**/dist", 31 | ] 32 | } -------------------------------------------------------------------------------- /src/transformer/visitor/taggedTemplateExpressionVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as constant from '../constant' 5 | import processAsm from './function/processAsm' 6 | 7 | export default function (node: ts.TaggedTemplateExpression, visitor: ts.Visitor): ts.Node { 8 | if (ts.isIdentifier(node.tag) && (node.tag.escapedText === constant.tagAsm || node.tag.escapedText === constant.tagAsm64)) { 9 | const template = ts.visitNode(node.template, visitor) as ts.TemplateExpression 10 | return processAsm(template, node, node.tag.escapedText === constant.tagAsm64) 11 | } 12 | return ts.visitEachChild(node, visitor, statement.context) 13 | } 14 | -------------------------------------------------------------------------------- /src/std/function/getRandomValues.ts: -------------------------------------------------------------------------------- 1 | if (defined(ENV_NODE) && !defined(ENV_CJS)) { 2 | // @ts-ignore 3 | import crypto from 'crypto' 4 | } 5 | 6 | let crypto_: Crypto 7 | 8 | if (defined(ENV_NODE)) { 9 | if (defined(ENV_CJS)) { 10 | crypto_ = require('crypto') 11 | } 12 | else { 13 | crypto_ = crypto as globalThis.Crypto 14 | } 15 | } 16 | else { 17 | if (typeof crypto !== 'undefined') { 18 | crypto_ = crypto as globalThis.Crypto 19 | } 20 | } 21 | 22 | export default function getRandomValues(buffer: Uint8Array) { 23 | if (defined(ENV_NODE)) { 24 | // @ts-ignore 25 | crypto_.randomFillSync(buffer) 26 | } 27 | else { 28 | crypto_.getRandomValues(buffer) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/transformer/__test__/clear.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { fileURLToPath } from 'url' 4 | 5 | const distPath = path.join(path.dirname(fileURLToPath(import.meta.url)), './__test__cache') 6 | 7 | function removeDir(dir) { 8 | let files = fs.readdirSync(dir) 9 | for (let i = 0; i < files.length; i++) { 10 | let newPath = path.join(dir, files[i]) 11 | let stat = fs.statSync(newPath) 12 | if (stat.isDirectory()) { 13 | removeDir(newPath) 14 | } 15 | else { 16 | fs.unlinkSync(newPath) 17 | } 18 | } 19 | fs.rmdirSync(dir) 20 | } 21 | 22 | export default function () { 23 | if (fs.existsSync(distPath)) { 24 | removeDir(distPath) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/std/unmake.ts: -------------------------------------------------------------------------------- 1 | import { symbolStruct, symbolStructAddress } from '../symbol' 2 | import { revokeProxyStruct } from '../proxyStruct' 3 | import { revokeDefinedStruct } from '../definedStruct' 4 | import { support } from '@libmedia/common' 5 | 6 | /** 7 | * 销毁一个 struct 实例,调用 make 创建的对象必须调用 unmake,否则内存泄漏 8 | * 9 | * @param target 10 | */ 11 | export default function unmake(target: T) { 12 | 13 | assert(Object.getPrototypeOf(target)[symbolStruct], 'cannot unmake struct because of not defined') 14 | 15 | const p = target[symbolStructAddress] 16 | if (p) { 17 | free(p) 18 | target[symbolStructAddress] = nullptr 19 | support.proxy ? revokeProxyStruct(target) : revokeDefinedStruct(target) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/transformer/function/getImportIdentifier.ts: -------------------------------------------------------------------------------- 1 | import statement from '../statement' 2 | 3 | export default function getImportIdentifier(map: Map, name: string, defaultExport: boolean = false) { 4 | if (!map) { 5 | return 6 | } 7 | if (map.has('all')) { 8 | return statement.context.factory.createPropertyAccessExpression( 9 | statement.context.factory.createIdentifier(map.get('all')), 10 | statement.context.factory.createIdentifier(name) 11 | ) 12 | } 13 | else if (defaultExport && map.has('default')) { 14 | return statement.context.factory.createIdentifier(map.get('default')) 15 | } 16 | else if (map.has(name)) { 17 | return statement.context.factory.createIdentifier(map.get(name)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /include/wasmsemaphore.h: -------------------------------------------------------------------------------- 1 | #ifndef __WASM_SEMAPHORE_H__ 2 | #define __WASM_SEMAPHORE_H__ 3 | 4 | #include "./wasmenv.h" 5 | #include "./wasmpthread.h" 6 | 7 | struct sem { 8 | int atomic; 9 | struct pthread_mutex mutex; 10 | }; 11 | 12 | typedef struct sem wasm_sem_t; 13 | 14 | // 创建和销毁信号量 15 | EM_PORT_API(int) wasm_sem_init(wasm_sem_t *sem, int pshared, unsigned int value); 16 | EM_PORT_API(int) wasm_sem_destroy(wasm_sem_t *sem); 17 | 18 | // 信号量操作 19 | EM_PORT_API(int) wasm_sem_wait(wasm_sem_t *sem); 20 | EM_PORT_API(int) wasm_sem_trywait(wasm_sem_t *sem); 21 | EM_PORT_API(int) wasm_sem_timedwait(wasm_sem_t* sem, const struct timespec* abstime); 22 | EM_PORT_API(int) wasm_sem_post(wasm_sem_t *sem); 23 | #endif -------------------------------------------------------------------------------- /src/thread/runThread.ts: -------------------------------------------------------------------------------- 1 | import { is } from '@libmedia/common' 2 | import initClass from './initClass' 3 | import initFunction from './initFunction' 4 | import initModule from './initModule' 5 | 6 | type AnyModule = { 7 | [key: string]: any 8 | } 9 | 10 | /** 11 | * 子线程运行入口方法 12 | * 13 | * @param entity 14 | */ 15 | export default function runThread(entity: (new (...args: any[]) => any) | ((...args: any[]) => any) | AnyModule) { 16 | if (is.func(entity) && entity.prototype?.constructor === entity) { 17 | initClass((args: any[]) => { 18 | return new (entity as new (...args: any[]) => any)(...args) 19 | }) 20 | } 21 | else if (is.func(entity)) { 22 | initFunction((args: any[]) => { 23 | return entity(...args) 24 | }) 25 | } 26 | else { 27 | initModule(entity) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /include/wasmenv.h: -------------------------------------------------------------------------------- 1 | #ifndef __WASM_ENV_H__ 2 | #define __WASM_ENV_H__ 3 | /** 4 | * 声明导出为 js 调用函数 5 | **/ 6 | 7 | #ifndef EM_PORT_API 8 | #if defined(__EMSCRIPTEN__) 9 | #include 10 | #if defined(__cplusplus) 11 | #define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE 12 | #else 13 | #define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE 14 | #endif 15 | #else 16 | #if defined(__cplusplus) 17 | #define EM_PORT_API(rettype) extern "C" rettype 18 | #else 19 | #define EM_PORT_API(rettype) rettype 20 | #endif 21 | #endif 22 | #endif 23 | 24 | EM_PORT_API(int) wasm_pthread_support(); 25 | EM_PORT_API(int) wasm_cpu_core_count(); 26 | EM_PORT_API(int) wasm_threw_error(int code, const char* msg); 27 | 28 | #endif -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | roots: ['/'], 5 | transform: { 6 | '^.+\\.tsx?$': [ 7 | 'ts-jest', 8 | { 9 | diagnostics: { 10 | ignoreCodes: [1343] 11 | }, 12 | astTransformers: { 13 | before: [ 14 | { 15 | path: 'node_modules/ts-jest-mock-import-meta', 16 | options: { metaObjectReplacement: { url: './' } } 17 | } 18 | ] 19 | } 20 | } 21 | ] 22 | }, 23 | moduleNameMapper: { 24 | '^cheap/(.*)$': '/src/$1', 25 | '^@libmedia/common$': '/../common/src' 26 | }, 27 | testMatch: ['/src/transformer/__test__/**/*.test.ts'], 28 | globalTeardown: '/src/transformer/__test__/clear.js' 29 | } 30 | -------------------------------------------------------------------------------- /src/transformer/visitor/bindingElementVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as typeUtils from '../util/typeutil' 5 | import * as nodeUtils from '../util/nodeutil' 6 | 7 | export default function (node: ts.BindingElement, visitor: ts.Visitor): ts.Node | ts.Node[] { 8 | if (node.initializer && node.pos > -1) { 9 | const type = statement.typeChecker.getTypeAtLocation(node.name) 10 | if (typeUtils.isSizeType(type, true)) { 11 | return statement.context.factory.createBindingElement( 12 | node.dotDotDotToken, 13 | node.propertyName, 14 | node.name, 15 | ts.visitNode(nodeUtils.createPointerOperand(node.initializer), statement.visitor) as ts.Expression 16 | ) 17 | } 18 | } 19 | return ts.visitEachChild(node, visitor, statement.context) 20 | } 21 | -------------------------------------------------------------------------------- /src/transformer/function/isMergeOperator.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | export default function isMergeOperator(operator: ts.SyntaxKind) { 3 | return operator === ts.SyntaxKind.PlusEqualsToken 4 | || operator === ts.SyntaxKind.MinusEqualsToken 5 | || operator === ts.SyntaxKind.AsteriskEqualsToken 6 | || operator === ts.SyntaxKind.AsteriskAsteriskEqualsToken 7 | || operator === ts.SyntaxKind.SlashEqualsToken 8 | || operator === ts.SyntaxKind.PercentEqualsToken 9 | || operator === ts.SyntaxKind.LessThanLessThanEqualsToken 10 | || operator === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken 11 | || operator === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken 12 | || operator === ts.SyntaxKind.AmpersandEqualsToken 13 | || operator === ts.SyntaxKind.BarEqualsToken 14 | || operator === ts.SyntaxKind.CaretEqualsToken 15 | } 16 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/asm.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('args', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './asm_input.ts') 12 | output = path.join(distPath, './asm_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | 21 | test('asm', () => { 22 | const source = ` 23 | const text = asm\` 24 | (func $write16 (export "write16") (param $p0 i32) (param $p1 i32) 25 | (local.get $p0) 26 | (local.get $p1) 27 | (i32.store16) 28 | ) 29 | \` 30 | ` 31 | const target = ` 32 | const text = "AGFzbQEAAAABBgFgAn9/AAISAQNlbnYGbWVtb3J5AgMBgIAEAwIBAAcLAQd3cml0ZTE2AAAKCwEJACAAIAE7AQAL"; 33 | ` 34 | check(source, target, { 35 | input 36 | }) 37 | }) 38 | }) -------------------------------------------------------------------------------- /src/webassembly/thread.ts: -------------------------------------------------------------------------------- 1 | import type { Cond } from '../thread/cond' 2 | import type { Mutex } from '../thread/mutex' 3 | 4 | @struct 5 | export class PthreadOnce { 6 | atomic: atomic_int32 7 | } 8 | 9 | @struct 10 | export class Pthread { 11 | id: int32 12 | retval: pointer 13 | flags: int32 14 | status: atomic_int32 15 | } 16 | 17 | @struct 18 | export class ThreadDescriptor { 19 | flags: int32 20 | status: PthreadStatus 21 | } 22 | 23 | export const enum PthreadFlags { 24 | DETACH = 1, 25 | POOL = 2, 26 | EXIT = 4 27 | } 28 | 29 | export const enum PthreadStatus { 30 | STOP, 31 | RUN 32 | } 33 | 34 | export type ChildThread = { 35 | thread: pointer 36 | worker: Worker 37 | stackPointer: pointer 38 | threadDescriptor: pointer 39 | } 40 | 41 | @struct 42 | export class ThreadWait { 43 | thread: pointer 44 | func: pointer<(args: pointer) => void> 45 | args: pointer 46 | cond: Cond 47 | mutex: Mutex 48 | } 49 | -------------------------------------------------------------------------------- /src/transformer/visitor/parameterVisitor.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | import statement, { StageStatus } from '../statement' 3 | import * as typeUtils from '../util/typeutil' 4 | import * as nodeUtils from '../util/nodeutil' 5 | 6 | export default function (node: ts.ParameterDeclaration, visitor: ts.Visitor): ts.Node | ts.Node[] { 7 | 8 | statement.pushStage(StageStatus.Parameter) 9 | 10 | if (node.initializer && node.type && node.pos > -1) { 11 | const type = statement.typeChecker.getTypeAtLocation(node.type) 12 | if (typeUtils.isSizeType(type)) { 13 | return ts.visitNode(statement.context.factory.createParameterDeclaration( 14 | node.modifiers, 15 | node.dotDotDotToken, 16 | node.name, 17 | node.questionToken, 18 | node.type, 19 | nodeUtils.createPointerOperand(node.initializer) 20 | ), statement.visitor) 21 | } 22 | } 23 | const newNode = ts.visitEachChild(node, visitor, statement.context) 24 | 25 | statement.popStage() 26 | 27 | return newNode 28 | } 29 | -------------------------------------------------------------------------------- /src/webassembly/runtime/semaphore.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 3 | import * as semUtils from '../../thread/semaphore' 4 | import type { Sem } from '../../thread/semaphore' 5 | 6 | @struct 7 | export class Timespec { 8 | tvSec: int64 9 | tvNSec: int32 10 | } 11 | 12 | 13 | export function wasm_sem_init(sem: pointer, pshared: int32, value: uint32) { 14 | return semUtils.init(sem, value) 15 | } 16 | 17 | export function wasm_sem_destroy(sem: pointer) { 18 | return semUtils.destroy(sem) 19 | } 20 | 21 | export function wasm_sem_wait(sem: pointer) { 22 | return semUtils.wait(sem) 23 | } 24 | 25 | export function wasm_sem_trywait(sem: pointer) { 26 | return semUtils.tryWait(sem) 27 | } 28 | 29 | export function wasm_sem_timedwait(sem: pointer, abstime: pointer) { 30 | let timeout = Number(abstime.tvSec) * 1000 + abstime.tvNSec / 1000000 31 | return semUtils.timedWait(sem, timeout) 32 | } 33 | 34 | export function wasm_sem_post(sem: pointer) { 35 | return semUtils.post(sem) 36 | } 37 | -------------------------------------------------------------------------------- /src/transformer/visitor/function/addImportStatements.ts: -------------------------------------------------------------------------------- 1 | import type { ImportData } from '../../type' 2 | import statement from '../../statement' 3 | 4 | export default function addImportStatements(imports: ImportData[], path: string, updatedStatements: any[]) { 5 | if (imports.length) { 6 | const importElements = [] 7 | 8 | imports.forEach((item) => { 9 | importElements.push(statement.context.factory.createImportSpecifier( 10 | false, 11 | statement.context.factory.createIdentifier(item.name), 12 | statement.context.factory.createIdentifier(item.formatName) 13 | )) 14 | }) 15 | 16 | const importDeclaration = statement.context.factory.createImportDeclaration( 17 | undefined, 18 | statement.context.factory.createImportClause( 19 | false, 20 | undefined, 21 | statement.context.factory.createNamedImports(importElements) 22 | ), 23 | statement.context.factory.createStringLiteral(path) 24 | ) 25 | 26 | updatedStatements.push(importDeclaration) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Gaoxing Zhao. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/transformer/visitor/ifStatementVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as nodeUtil from '../util/nodeutil' 5 | 6 | function check(node: ts.Node) { 7 | if (node 8 | && ts.isBlock(node) 9 | && !node.statements.some((n) => !ts.isImportDeclaration(n)) 10 | ) { 11 | if (node.statements.length === 1) { 12 | return node.statements[0] 13 | } 14 | return node.statements 15 | } 16 | return node 17 | } 18 | 19 | export default function (node: ts.IfStatement, visitor: ts.Visitor): ts.Node | ts.Node[] { 20 | if (ts.visitNode(node.expression, nodeUtil.hasDefined) && ts.visitNode(node.expression, nodeUtil.checkConditionCompile)) { 21 | if (nodeUtil.checkBool(node.expression, visitor)) { 22 | return check(ts.visitNode(node.thenStatement, visitor)) as ts.Node 23 | } 24 | else if (node.elseStatement) { 25 | return check(ts.visitNode(node.elseStatement, visitor)) as ts.Node 26 | } 27 | else { 28 | return undefined 29 | } 30 | } 31 | return ts.visitEachChild(node, visitor, statement.context) 32 | } 33 | -------------------------------------------------------------------------------- /src/transformer/function/pushImport.ts: -------------------------------------------------------------------------------- 1 | import type { ImportData, RequireData } from '../type' 2 | 3 | export function pushImport( 4 | keys: ImportData[], 5 | name: string, 6 | path: string, 7 | formatName: string, 8 | defaultExport: boolean 9 | ) { 10 | for (let i = 0; i < keys.length; i++) { 11 | if (keys[i].name === name && keys[i].path === path) { 12 | return keys[i] 13 | } 14 | } 15 | 16 | const item = { 17 | name, 18 | path, 19 | default: defaultExport, 20 | formatName 21 | } 22 | 23 | keys.push(item) 24 | 25 | return item as ImportData 26 | } 27 | 28 | export function pushRequire( 29 | keys: RequireData[], 30 | formatName: string, 31 | path: string, 32 | defaultExport: boolean, 33 | esModule: boolean 34 | ) { 35 | for (let i = 0; i < keys.length; i++) { 36 | if (keys[i].path === path && keys[i].default === defaultExport) { 37 | return keys[i] 38 | } 39 | } 40 | 41 | const item = { 42 | formatName, 43 | path, 44 | default: defaultExport, 45 | esModule 46 | } 47 | 48 | keys.push(item) 49 | 50 | return item as RequireData 51 | } 52 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/mergeOperator.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('merge operator', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './merge_operator_input.ts') 12 | output = path.join(distPath, './merge_operator_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | 21 | test('pointer += 1', () => { 22 | const source = ` 23 | let a: pointer 24 | a += 1 25 | ` 26 | const target = ` 27 | let a: pointer 28 | a = a + 1 29 | ` 30 | check(source, target, { 31 | input 32 | }) 33 | }) 34 | 35 | test('pointer -= 1', () => { 36 | const source = ` 37 | let a: pointer 38 | a -= 1 39 | ` 40 | const target = ` 41 | let a: pointer 42 | a = a - 1 43 | ` 44 | check(source, target, { 45 | input 46 | }) 47 | }) 48 | 49 | }) -------------------------------------------------------------------------------- /src/std/make.ts: -------------------------------------------------------------------------------- 1 | import { memset } from './memory' 2 | import { symbolStruct } from '../symbol' 3 | import structAccess from './mapStruct' 4 | 5 | import { 6 | isDef, 7 | object, 8 | type Data 9 | } from '@libmedia/common' 10 | 11 | /** 12 | * 创建一个 struct 实例 13 | * 14 | * @param target 15 | * @returns 16 | */ 17 | export default function make(init?: Data, struct?: new (...args: any[]) => T): T { 18 | 19 | if (!isDef(struct)) { 20 | struct = init as unknown as new (...args: any[]) => any 21 | init = null 22 | } 23 | 24 | assert(struct.prototype[symbolStruct], 'cannot make struct because of not defined') 25 | 26 | const size = sizeof(struct) 27 | const address = malloc(size) 28 | if (!address) { 29 | throw new TypeError('cannot alloc memory for struct') 30 | } 31 | 32 | memset(address, 0, size) 33 | 34 | const target = structAccess(address, struct) 35 | 36 | const data = new struct() 37 | 38 | if (init) { 39 | object.extend(data, init) 40 | } 41 | 42 | object.each(data, (value, key) => { 43 | if (isDef(value)) { 44 | target[key] = value 45 | } 46 | }) 47 | 48 | return target 49 | } 50 | -------------------------------------------------------------------------------- /src/transformer/type.ts: -------------------------------------------------------------------------------- 1 | import type ts from 'typescript' 2 | 3 | 4 | export interface ImportData { 5 | name: string 6 | path: string 7 | default: boolean 8 | formatName: string 9 | } 10 | 11 | export interface RequireData { 12 | formatName: string 13 | path: string 14 | default: boolean 15 | defaultName?: string 16 | esModule?: boolean 17 | } 18 | 19 | export interface DeclarationData { 20 | name: string 21 | formatName: string 22 | initializer?: ts.Expression 23 | } 24 | 25 | export interface TransformerOptions { 26 | tmpPath?: string 27 | projectPath?: string 28 | cheapSourcePath?: string 29 | formatIdentifier?: boolean 30 | module?: string 31 | exclude?: RegExp | RegExp[] 32 | defined?: Record 33 | cheapPacketName?: string 34 | importPath?: (path: string) => string 35 | reportError?: (error: { 36 | file: string, 37 | loc: { 38 | start: { 39 | line: number 40 | column: number 41 | } 42 | end: { 43 | line: number 44 | column: number 45 | } 46 | } 47 | code: number, 48 | message: string 49 | }) => void, 50 | wat2wasm?: string 51 | } 52 | -------------------------------------------------------------------------------- /src/webassembly/runtime/asm/libc.asm.ts: -------------------------------------------------------------------------------- 1 | export default asm` 2 | (func $env.malloc (;0;) (import "env" "malloc") (param i32) (result i32)) 3 | (func $env.free (;1;) (import "env" "free") (param i32)) 4 | (func $env.calloc (;2;) (import "env" "calloc") (param i32 i32) (result i32)) 5 | (func $env.realloc (;3;) (import "env" "realloc") (param i32 i32) (result i32)) 6 | (func $env.aligned_alloc (;4;) (import "env" "aligned_alloc") (param i32 i32) (result i32)) 7 | 8 | (func $malloc (export "malloc") (param $size i32) (result i32) 9 | (call $env.malloc (local.get $size)) 10 | ) 11 | (func $free (export "free") (param $pointer i32) 12 | (call $env.free (local.get $pointer)) 13 | ) 14 | (func $calloc (export "calloc") (param $num i32) (param $size i32) (result i32) 15 | (call $env.calloc (local.get $num) (local.get $size)) 16 | ) 17 | (func $realloc (export "realloc") (param $pointer i32) (param $size i32) (result i32) 18 | (call $env.realloc (local.get $pointer) (local.get $size)) 19 | ) 20 | (func $aligned_alloc (export "alignedAlloc") (param $alignment i32) (param $size i32) (result i32) 21 | (call $env.realloc (local.get $alignment) (local.get $size)) 22 | ) 23 | ` 24 | -------------------------------------------------------------------------------- /src/webassembly/runtime/asm/libc64.asm.ts: -------------------------------------------------------------------------------- 1 | export default asm64` 2 | (func $env.malloc (;0;) (import "env" "malloc") (param i64) (result i64)) 3 | (func $env.free (;1;) (import "env" "free") (param i64)) 4 | (func $env.calloc (;2;) (import "env" "calloc") (param i64 i64) (result i64)) 5 | (func $env.realloc (;3;) (import "env" "realloc") (param i64 i64) (result i64)) 6 | (func $env.aligned_alloc (;4;) (import "env" "aligned_alloc") (param i64 i64) (result i64)) 7 | 8 | (func $malloc (export "malloc") (param $size i64) (result i64) 9 | (call $env.malloc (local.get $size)) 10 | ) 11 | (func $free (export "free") (param $pointer i64) 12 | (call $env.free (local.get $pointer)) 13 | ) 14 | (func $calloc (export "calloc") (param $num i64) (param $size i64) (result i64) 15 | (call $env.calloc (local.get $num) (local.get $size)) 16 | ) 17 | (func $realloc (export "realloc") (param $pointer i64) (param $size i64) (result i64) 18 | (call $env.realloc (local.get $pointer) (local.get $size)) 19 | ) 20 | (func $aligned_alloc (export "alignedAlloc") (param $alignment i64) (param $size i64) (result i64) 21 | (call $env.realloc (local.get $alignment) (local.get $size)) 22 | ) 23 | ` 24 | -------------------------------------------------------------------------------- /src/std/mapStruct.ts: -------------------------------------------------------------------------------- 1 | import { symbolStruct, symbolStructKeysMeta } from '../symbol' 2 | import { proxyStruct } from '../proxyStruct' 3 | import { definedStruct } from '../definedStruct' 4 | import type { KeyMeta } from '../typedef' 5 | import { KeyMetaKey } from '../typedef' 6 | 7 | import { support, is, keypath } from '@libmedia/common' 8 | 9 | /** 10 | * 访问 struct 指针 11 | * 12 | * @param target 13 | * @param address 14 | * @returns 15 | */ 16 | export default function mapStruct(address: pointer, struct: new (...args: any[]) => T): T { 17 | 18 | assert(struct.prototype[symbolStruct], 'cannot reinterpret cast struct because of not defined') 19 | 20 | if (arguments[2] && is.string(arguments[2])) { 21 | struct = struct.prototype 22 | keypath.each(arguments[2], (key) => { 23 | const meta = struct[symbolStructKeysMeta] as Map 24 | struct = meta.get(key)[KeyMetaKey.Type] as (new (...args: any[]) => T) 25 | }) 26 | } 27 | 28 | if (defined(WASM_64)) { 29 | return proxyStruct(address, struct) 30 | } 31 | else { 32 | return support.proxy ? proxyStruct(address, struct) : definedStruct(address, struct) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/transformer/function/compute.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | 3 | export function compute(a: T, b: T, token: ts.SyntaxKind): T { 4 | if (token === ts.SyntaxKind.PlusToken) { 5 | return ((a as number) + (b as number)) as T 6 | } 7 | else if (token === ts.SyntaxKind.MinusToken) { 8 | return (a - b) as T 9 | } 10 | else if (token === ts.SyntaxKind.AsteriskToken) { 11 | return (a * b) as T 12 | } 13 | else if (token === ts.SyntaxKind.AsteriskAsteriskToken) { 14 | return (a ** b) as T 15 | } 16 | else if (token === ts.SyntaxKind.SlashToken) { 17 | return (a / b) as T 18 | } 19 | else if (token === ts.SyntaxKind.PercentToken) { 20 | return (a % b) as T 21 | } 22 | else if (token === ts.SyntaxKind.LessThanLessThanToken) { 23 | return (a << b) as T 24 | } 25 | else if (token === ts.SyntaxKind.GreaterThanGreaterThanToken) { 26 | return (a >> b) as T 27 | } 28 | else if (token === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) { 29 | return (a >>> b) as T 30 | } 31 | else if (token === ts.SyntaxKind.AmpersandToken) { 32 | return (a & b) as T 33 | } 34 | else if (token === ts.SyntaxKind.BarToken) { 35 | return (a | b) as T 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { SELF } from '@libmedia/common/constant' 2 | import { support, browser, os } from '@libmedia/common' 3 | 4 | const ENV: { 5 | CHEAP_DISABLE_THREAD: boolean 6 | CHEAP_HEAP_INITIAL: number 7 | CHEAP_HEAP_MAXIMUM: number 8 | } = defined(ENV_NODE) ? process.env : SELF 9 | 10 | /** 11 | * 是否使用多线程 12 | */ 13 | export const USE_THREADS = defined(ENABLE_THREADS) && (support.thread || defined(ENV_NODE)) && !ENV.CHEAP_DISABLE_THREAD 14 | 15 | /** 16 | * 栈地址对齐 17 | * 栈地址至少是 16 字节对齐,因为 wasm 的基本类型中最大是 v128 16 字节 18 | */ 19 | export let STACK_ALIGNMENT = 16 20 | 21 | /** 22 | * 栈大小,应为 STACK_ALIGNMENT 的整数倍 23 | */ 24 | export let STACK_SIZE = 1024 * 1024 25 | 26 | /** 27 | * 堆保留段,可用于静态数据区分配 28 | */ 29 | export const HEAP_OFFSET = 1024 30 | 31 | /** 32 | * 堆初始大小 33 | */ 34 | export const HEAP_INITIAL: uint32 = (ENV.CHEAP_HEAP_INITIAL ?? defined(CHEAP_HEAP_INITIAL)) 35 | 36 | /** 37 | * 堆最大大小 38 | * ios safari 16 以下 对最大值有限制,太大分配不出来 39 | */ 40 | export const HEAP_MAXIMUM: uint32 = ENV.CHEAP_HEAP_MAXIMUM 41 | ?? (USE_THREADS && (os.ios && !browser.checkVersion(os.version, '17', true)) 42 | ? 8192 43 | : (defined(WASM_64) 44 | // 64 位最大 16GB 45 | ? 262144 46 | // 32 位最大 4GB 47 | : 65536 48 | ) 49 | ) 50 | -------------------------------------------------------------------------------- /src/thread/barrier.ts: -------------------------------------------------------------------------------- 1 | import * as atomics from './atomics' 2 | 3 | @struct 4 | export class Barrier { 5 | counter: atomic_int32 6 | atomic: atomic_int32 7 | numAgents: atomic_int32 8 | } 9 | 10 | /** 11 | * 初始化 Barrier 12 | * @returns 13 | */ 14 | export function init(barrier: pointer, numAgents: int32): int32 { 15 | atomics.store(addressof(barrier.counter), numAgents) 16 | atomics.store(addressof(barrier.atomic), 0) 17 | atomics.store(addressof(barrier.numAgents), numAgents) 18 | return 0 19 | } 20 | 21 | /** 22 | * Enter the barrier 23 | * This will block until all agents have entered the barrier 24 | * 25 | * @param barrier 26 | */ 27 | export function enter(barrier: pointer): int32 { 28 | const atomic = atomics.load(addressof(barrier.atomic)) 29 | if (atomics.sub(addressof(barrier.counter), 1) === 1) { 30 | const numAgents = barrier.numAgents 31 | barrier.counter = numAgents 32 | atomics.add(addressof(barrier.atomic), 1) 33 | atomics.notify(addressof(barrier.atomic), numAgents - 1) 34 | atomics.add(addressof(barrier.atomic), 1) 35 | } 36 | else { 37 | atomics.wait(addressof(barrier.atomic), atomic) 38 | while (atomics.load(addressof(barrier.atomic)) & 1) {} 39 | } 40 | return 0 41 | } 42 | -------------------------------------------------------------------------------- /src/asm/ASMRunner.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Memory } from '../heap' 3 | import * as config from '../config' 4 | 5 | import { 6 | base64, 7 | logger, 8 | wasm as wasmUtils 9 | } from '@libmedia/common' 10 | 11 | export default class ASMRunner { 12 | 13 | private runner: WebAssembly.Instance 14 | 15 | private wasm: Uint8Array 16 | 17 | constructor(asmBase64: string) { 18 | this.wasm = wasmUtils.setMemoryMeta(base64.base64ToUint8Array(asmBase64), { 19 | shared: config.USE_THREADS && defined(ENABLE_THREADS), 20 | initial: config.HEAP_INITIAL, 21 | maximum: config.HEAP_MAXIMUM 22 | }) 23 | const module = new WebAssembly.Module(this.wasm as BufferSource) 24 | this.runner = new WebAssembly.Instance(module, { 25 | env: { 26 | memory: Memory 27 | } 28 | }) 29 | } 30 | 31 | public invoke(name: string, ...args: (number | bigint)[]) { 32 | if (this.runner.exports[name]) { 33 | return (this.runner.exports[name] as Function).apply(null, args) 34 | } 35 | else { 36 | logger.fatal(`not found export function ${name} in ASMRunner`) 37 | } 38 | } 39 | 40 | public destroy() { 41 | this.runner = null 42 | this.wasm = null 43 | } 44 | 45 | get exports() { 46 | return this.runner.exports 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/allocator/Allocator.ts: -------------------------------------------------------------------------------- 1 | export default interface Allocator { 2 | 3 | addUpdateHandle(handle: (buffer: ArrayBufferLike) => void): void 4 | removeUpdateHandle(handle: (buffer: ArrayBufferLike) => void): void 5 | 6 | /** 7 | * 分配一个长度是 size 的一段内存 8 | * 9 | * @param size 10 | */ 11 | malloc(size: size): pointer 12 | 13 | /** 14 | * 分配一块指定数量的内存,并将其初始化为零 15 | * 16 | * @param num 要分配的元素数量 17 | * @param size 每个元素的大小(以字节为单位) 18 | */ 19 | calloc(num: size, size: size): pointer 20 | 21 | /** 22 | * 重新调整已分配内存块的大小 23 | * 24 | * @param address 已分配内存块的指针 25 | * @param size 调整的内存块的新大小(以字节为单位) 26 | */ 27 | realloc(address: pointer, size: size): pointer 28 | 29 | /** 30 | * 堆上分配一块对齐的内存块 31 | * 32 | * @param alignment 内存对齐的要求 33 | * @param size 分配的内存块的大小(以字节为单位) 34 | */ 35 | alignedAlloc(alignment: size, size: size): pointer 36 | 37 | /** 38 | * 释放指定地址的内存 39 | * 40 | * @param address 41 | */ 42 | free(address: pointer): void 43 | 44 | /** 45 | * 判断给定地址是否已经被分配 46 | * 47 | * @param pointer 48 | */ 49 | isAlloc(pointer: pointer): boolean 50 | 51 | /** 52 | * 给出指定地址分配的内存大小 53 | * 54 | * @param address 55 | */ 56 | sizeof(address: int32): size 57 | 58 | getBuffer(): ArrayBufferLike 59 | } 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "sourceMap": true, 5 | "noImplicitAny": false, 6 | "noUnusedLocals": false, 7 | "noUnusedParameters": false, 8 | "moduleResolution": "node", 9 | "declaration": true, 10 | "downlevelIteration": true, 11 | "declarationMap": false, 12 | "module": "esnext", 13 | "target": "esnext", 14 | "esModuleInterop": true, 15 | "baseUrl": "./", 16 | "paths": { 17 | "@libmedia/cheap": ["./*"], 18 | "@libmedia/common": ["../common/src"], 19 | "@libmedia/common/io": ["../common/src/io"], 20 | "@libmedia/common/network": ["../common/src/network"], 21 | "@libmedia/common/math": ["../common/src/math"], 22 | "@libmedia/common/sourceLoad": ["../common/src/function/sourceLoad"], 23 | "@libmedia/common/constant": ["../common/src/util/constant"] 24 | }, 25 | "typeRoots": [ 26 | "./node_modules/@types", 27 | ], 28 | "types": [ 29 | "node", 30 | "jest" 31 | ], 32 | "lib": [ 33 | "dom", 34 | "ESNext" 35 | ], 36 | "allowJs": true 37 | }, 38 | "include": [ 39 | "./src/**/*.ts", 40 | "@types/index.d.ts", 41 | "../common/src/types/*.ts", 42 | ], 43 | "exclude": [ 44 | "*.test.ts", 45 | "__test__", 46 | "/node_modules/**", 47 | "**/dist", 48 | ] 49 | } -------------------------------------------------------------------------------- /src/staticData.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 静态分配区只能在此文件分配 3 | * 导出在其他地方使用 4 | */ 5 | 6 | import * as config from './config' 7 | import { Mutex } from './thread/mutex' 8 | 9 | /** 10 | * 静态分配区栈顶指针 11 | */ 12 | let pointer: pointer = reinterpret_cast>(reinterpret_cast>>(nullptr) + 1) 13 | 14 | export function malloc(length: size, algin: size = 1): pointer { 15 | 16 | let address = pointer 17 | 18 | while (address % algin) { 19 | address++ 20 | } 21 | 22 | assert(address < config.HEAP_OFFSET, 'static data overflow') 23 | 24 | pointer = reinterpret_cast>(address + length) 25 | 26 | return address 27 | } 28 | 29 | /** 30 | * 线程计数器地址 31 | */ 32 | export const threadCounter: pointer = reinterpret_cast>(malloc(sizeof(uint32), sizeof(uint32))) 33 | 34 | /** 35 | * 堆分配锁地址 36 | */ 37 | export const heapMutex: pointer = reinterpret_cast>(malloc(sizeof(Mutex), sizeof(atomic_int32))) 38 | 39 | /** 40 | * 32 位唯一 id 生成地址 41 | */ 42 | export const uniqueCounter32: pointer = reinterpret_cast>(malloc(sizeof(atomic_uint32), sizeof(atomic_uint32))) 43 | 44 | /** 45 | * 64 位唯一 id 生成地址 46 | */ 47 | export const uniqueCounter64: pointer = reinterpret_cast>(malloc(sizeof(atomic_uint64), sizeof(atomic_uint64))) 48 | -------------------------------------------------------------------------------- /src/transformer/visitor/decoratorVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import { BuiltinDecorator } from '../defined' 5 | import * as nodeUtils from '../util/nodeutil' 6 | import { array } from '@libmedia/common' 7 | 8 | export default function (node: ts.Decorator, visitor: ts.Visitor): ts.Node { 9 | if (ts.isIdentifier(node.expression)) { 10 | const name = node.expression.escapedText as string 11 | if (array.has(BuiltinDecorator, name)) { 12 | return undefined 13 | } 14 | } 15 | else if (ts.isCallExpression(node.expression) && ts.isIdentifier(node.expression.expression)) { 16 | const name = node.expression.expression.escapedText as string 17 | if (array.has(BuiltinDecorator, name)) { 18 | return undefined 19 | } 20 | } 21 | return ts.visitEachChild(node, visitor, statement.context) 22 | } 23 | 24 | export function asyncVisitor(node: ts.AsyncKeyword, visitor: ts.Visitor) { 25 | if (statement.cheapCompilerOptions.defined.ENABLE_SYNCHRONIZE_API 26 | && node.parent 27 | && (ts.isFunctionDeclaration(node.parent) 28 | || ts.isFunctionExpression(node.parent) 29 | || ts.isArrowFunction(node.parent) 30 | || ts.isMethodDeclaration(node.parent) 31 | ) 32 | && nodeUtils.isSynchronizeFunction(node.parent) 33 | ) { 34 | return undefined 35 | } 36 | return node 37 | } 38 | -------------------------------------------------------------------------------- /src/transformer/function/mergeOperator2Operator.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | export default function mergeOperator2Operator(operator: ts.SyntaxKind) { 3 | switch (operator) { 4 | case ts.SyntaxKind.PlusEqualsToken: 5 | return ts.SyntaxKind.PlusToken 6 | case ts.SyntaxKind.MinusEqualsToken: 7 | return ts.SyntaxKind.MinusToken 8 | case ts.SyntaxKind.AsteriskEqualsToken: 9 | return ts.SyntaxKind.AsteriskToken 10 | case ts.SyntaxKind.AsteriskAsteriskEqualsToken: 11 | return ts.SyntaxKind.AsteriskAsteriskToken 12 | case ts.SyntaxKind.SlashEqualsToken: 13 | return ts.SyntaxKind.SlashToken 14 | case ts.SyntaxKind.PercentEqualsToken: 15 | return ts.SyntaxKind.PercentToken 16 | case ts.SyntaxKind.LessThanLessThanEqualsToken: 17 | return ts.SyntaxKind.LessThanLessThanToken 18 | case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: 19 | return ts.SyntaxKind.GreaterThanGreaterThanToken 20 | case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: 21 | return ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken 22 | case ts.SyntaxKind.AmpersandEqualsToken: 23 | return ts.SyntaxKind.AmpersandToken 24 | case ts.SyntaxKind.BarEqualsToken: 25 | return ts.SyntaxKind.BarToken 26 | case ts.SyntaxKind.CaretEqualsToken: 27 | return ts.SyntaxKind.CaretToken 28 | default: 29 | return ts.SyntaxKind.EqualsToken 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/transformer/visitor/propertyAssignmentVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as typeUtils from '../util/typeutil' 5 | import * as nodeUtils from '../util/nodeutil' 6 | import reportError from '../function/reportError' 7 | import * as error from '../error' 8 | 9 | export default function (node: ts.PropertyAssignment, visitor: ts.Visitor): ts.Node | ts.Node[] { 10 | if (node.initializer && node.pos > -1) { 11 | const type = statement.typeChecker.getTypeAtLocation(node.name) 12 | const initType = statement.typeChecker.getTypeAtLocation(node.initializer) 13 | if (typeUtils.isPointerType(type, node.name) 14 | && (typeUtils.isBuiltinType(initType, node.initializer) || initType.flags & ts.TypeFlags.NumberLike) 15 | && !typeUtils.isPointerType(initType, node.initializer) 16 | && !typeUtils.isNullPointer(initType, node.initializer) 17 | ) { 18 | reportError(statement.currentFile, node, `type ${typeUtils.getBuiltinNameByType(initType) || 'number'} is not assignable to property assignment of type ${typeUtils.getBuiltinNameByType(type)}`, error.TYPE_MISMATCH) 19 | return node 20 | } 21 | else if (typeUtils.isSizeType(type, true)) { 22 | return statement.context.factory.createPropertyAssignment( 23 | node.name, 24 | ts.visitNode(nodeUtils.createPointerOperand(node.initializer), statement.visitor) as ts.Expression 25 | ) 26 | } 27 | } 28 | return ts.visitEachChild(node, visitor, statement.context) 29 | } 30 | -------------------------------------------------------------------------------- /src/transformer/visitor/propertyDeclarationVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as typeUtils from '../util/typeutil' 5 | import * as nodeUtils from '../util/nodeutil' 6 | import reportError from '../function/reportError' 7 | import * as error from '../error' 8 | 9 | export default function (node: ts.PropertyDeclaration, visitor: ts.Visitor): ts.Node | ts.Node[] { 10 | if (node.initializer && node.type && node.pos > -1) { 11 | const type = statement.typeChecker.getTypeAtLocation(node.type) 12 | const initType = statement.typeChecker.getTypeAtLocation(node.initializer) 13 | if (typeUtils.isPointerType(type, null) 14 | && (typeUtils.isBuiltinType(initType, node.initializer) || initType.flags & ts.TypeFlags.NumberLike) 15 | && !typeUtils.isPointerType(initType, node.initializer) 16 | && !typeUtils.isNullPointer(initType, node.initializer) 17 | ) { 18 | reportError(statement.currentFile, node, `type ${typeUtils.getBuiltinNameByType(initType) || 'number'} is not assignable to property declaration of type ${typeUtils.getBuiltinNameByType(type)}`, error.TYPE_MISMATCH) 19 | return node 20 | } 21 | else if (typeUtils.isSizeType(type)) { 22 | return ts.visitNode(statement.context.factory.createPropertyDeclaration( 23 | node.modifiers, 24 | node.name, 25 | node.questionToken || node.exclamationToken, 26 | node.type, 27 | nodeUtils.createPointerOperand(node.initializer) 28 | ), statement.visitor) 29 | } 30 | } 31 | return ts.visitEachChild(node, visitor, statement.context) 32 | } 33 | -------------------------------------------------------------------------------- /src/ctypeEnumRead.ts: -------------------------------------------------------------------------------- 1 | import type { CTypeEnum2Type } from './typedef' 2 | import { CTypeEnum } from './typedef' 3 | import { object } from '@libmedia/common' 4 | import unimplemented from './function/unimplemented' 5 | 6 | type CTypeEnumRead = { 7 | [key in CTypeEnum]: (pointer: pointer) => CTypeEnum2Type 8 | } 9 | 10 | export const CTypeEnumRead: CTypeEnumRead = { 11 | [CTypeEnum.char]: unimplemented, 12 | [CTypeEnum.atomic_char]: unimplemented, 13 | 14 | [CTypeEnum.uint8]: unimplemented, 15 | [CTypeEnum.atomic_uint8]: unimplemented, 16 | [CTypeEnum.uint16]: unimplemented, 17 | [CTypeEnum.atomic_uint16]: unimplemented, 18 | [CTypeEnum.uint32]: unimplemented, 19 | [CTypeEnum.atomic_uint32]: unimplemented, 20 | [CTypeEnum.uint64]: unimplemented, 21 | 22 | [CTypeEnum.int8]: unimplemented, 23 | [CTypeEnum.atomic_int8]: unimplemented, 24 | [CTypeEnum.int16]: unimplemented, 25 | [CTypeEnum.atomic_int16]: unimplemented, 26 | [CTypeEnum.int32]: unimplemented, 27 | [CTypeEnum.atomic_int32]: unimplemented, 28 | [CTypeEnum.int64]: unimplemented, 29 | 30 | [CTypeEnum.float]: unimplemented, 31 | [CTypeEnum.double]: unimplemented, 32 | [CTypeEnum.pointer]: unimplemented, 33 | 34 | [CTypeEnum.null]: unimplemented, 35 | [CTypeEnum.void]: unimplemented, 36 | 37 | [CTypeEnum.atomic_uint64]: unimplemented, 38 | [CTypeEnum.atomic_int64]: unimplemented, 39 | 40 | [CTypeEnum.bool]: unimplemented, 41 | [CTypeEnum.atomic_bool]: unimplemented, 42 | [CTypeEnum.size]: unimplemented 43 | } 44 | 45 | export function override(funcs: Partial) { 46 | object.extend(CTypeEnumRead, funcs) 47 | } 48 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/snippet.ts: -------------------------------------------------------------------------------- 1 | export const symbolImport = ` 2 | import { symbolStruct as symbolStruct, symbolStructMaxBaseTypeByteLength as symbolStructMaxBaseTypeByteLength, symbolStructLength as symbolStructLength, symbolStructKeysMeta as symbolStructKeysMeta } from "cheap/src/internal"; 3 | ` 4 | 5 | export const symbol2Import = ` 6 | import { symbolStruct as symbolStruct, symbolStructMaxBaseTypeByteLength as symbolStructMaxBaseTypeByteLength, symbolStructLength as symbolStructLength, symbolStructKeysMeta as symbolStructKeysMeta, symbolStructAddress as symbolStructAddress } from "cheap/src/internal"; 7 | ` 8 | 9 | export const definedMetaPropertyImport = ` 10 | import { definedMetaProperty as definedMetaProperty } from "cheap/src/internal"; 11 | ` 12 | export const ctypeEnumReadImport = ` 13 | import { CTypeEnumRead as CTypeEnumRead } from "cheap/src/internal"; 14 | ` 15 | export const ctypeEnumWriteImport = ` 16 | import { CTypeEnumWrite as CTypeEnumWrite } from "cheap/src/internal"; 17 | ` 18 | export const mapStructImport = ` 19 | import { mapStruct as mapStruct } from "cheap/src"; 20 | ` 21 | 22 | export const memcpyImport = ` 23 | import { memcpy as memcpy } from "cheap/src"; 24 | ` 25 | 26 | export const sizeofImport = ` 27 | import { sizeof as sizeof } from "cheap/src"; 28 | ` 29 | 30 | export const makeSharedPtrImport = ` 31 | import { makeSharedPtr as makeSharedPtr } from "cheap/src/internal"; 32 | ` 33 | 34 | export const makeImport = ` 35 | import { make as make } from "cheap/src"; 36 | ` 37 | 38 | export const allocatorImport = ` 39 | import { Allocator as Allocator } from 'cheap/src/internal'; 40 | ` 41 | -------------------------------------------------------------------------------- /src/webassembly/threadEntry.js: -------------------------------------------------------------------------------- 1 | var __CHeap_ThreadEntry__;(()=>{"use strict";var e={d:(r,t)=>{for(var n in t)e.o(t,n)&&!e.o(r,n)&&Object.defineProperty(r,n,{enumerable:!0,get:t[n]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},r={};function t(){let e;"object"==typeof __WebAssemblyRunner__?e=__WebAssemblyRunner__.__WebAssemblyRunner__:"object"==typeof __CHeap_WebAssemblyRunner__&&(e=__CHeap_WebAssemblyRunner__.default);let r,t,n=self,a=0;n.onmessage=s=>{const o=s.data,_=o.type,i=o.data;switch(_){case"run":n.postMessage({type:"run"}),self.CHeap&&self.CHeap.initThread&&self.CHeap.initThread(i.cheap).then((()=>{self.__SELF_THREAD__=i.runner.thread,i.runner.options.imports=self.imports,r=new e(i.runner.resource,i.runner.options),r.runAsChild(),e.getTable().get(i.runner.func)(i.runner.args),r.destroy()}));break;case"ready":t=i.runner,a=i.cheap.stackPointer,self.CHeap&&self.CHeap.initThread&&self.CHeap.initThread(i.cheap).then((()=>{n.postMessage({type:"ready"})}));break;case"wait":{async function p(){for(;;){for(e.mutexLock(a+16);0===e.readPointer(a);)e.condWait(a+12,a+16);self.__SELF_THREAD__=e.readPointer(a),t.options.thread=e.readPointer(a),t.thread=e.readPointer(a),t.func=e.readPointer(a+4),t.args=e.readPointer(a+8),r=new e(t.resource,t.options),r.runAsChild(),e.getTable().get(t.func)(t.args),r.destroy(),e.writePointer(a,0),e.mutexUnlock(a+16)}}t.options.imports=self.imports,p();break}case"import":importScripts(i.url),e=__CHeap_WebAssemblyRunner__.default}}}e.r(r),e.d(r,{default:()=>t}),t(),__CHeap_ThreadEntry__=r})(); -------------------------------------------------------------------------------- /src/transformer/function/reportError.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | import statement from '../statement' 3 | 4 | export default function reportError( 5 | file: ts.SourceFile, 6 | node: ts.Node, 7 | message: string, 8 | code: number = 9000, 9 | startPos: number = 0, 10 | endPos: number = 0 11 | ) { 12 | 13 | if (!startPos && node.pos > -1) { 14 | startPos = node.getStart() 15 | } 16 | if (!endPos && node.end > -1) { 17 | endPos = node.getEnd() 18 | } 19 | 20 | const format = ts.formatDiagnostic( 21 | { 22 | file: file, 23 | start: startPos, 24 | length: endPos - startPos, 25 | category: ts.DiagnosticCategory.Error, 26 | code, 27 | messageText: message 28 | }, 29 | { 30 | getCurrentDirectory: ts.sys.getCurrentDirectory, 31 | getCanonicalFileName: function (fileName: string): string { 32 | return fileName 33 | }, 34 | getNewLine: function (): string { 35 | return ts.sys.newLine 36 | } 37 | } 38 | ) 39 | if (statement.options.reportError) { 40 | const start = file.getLineAndCharacterOfPosition(startPos) 41 | const end = file.getLineAndCharacterOfPosition(endPos) 42 | statement.options.reportError({ 43 | file: file.fileName, 44 | loc: { 45 | start: { 46 | line: start.line + 1, 47 | column: start.character + 1 48 | }, 49 | end: { 50 | line: end.line + 1, 51 | column: end.character + 1 52 | } 53 | }, 54 | code, 55 | message: format 56 | }) 57 | } 58 | else { 59 | console.error('\x1b[31m%s\x1b[0m', format) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/transformer/visitor/variableDeclarationVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement, { StageStatus } from '../statement' 4 | import * as typeUtils from '../util/typeutil' 5 | import * as nodeUtils from '../util/nodeutil' 6 | import reportError from '../function/reportError' 7 | import * as error from '../error' 8 | 9 | export default function (node: ts.VariableDeclaration, visitor: ts.Visitor): ts.Node | ts.Node[] { 10 | 11 | statement.pushStage(StageStatus.VariableDeclaration) 12 | if (node.initializer && node.type && node.pos > -1) { 13 | const type = statement.typeChecker.getTypeAtLocation(node.type) 14 | const initType = statement.typeChecker.getTypeAtLocation(node.initializer) 15 | if (typeUtils.isPointerType(type, null) 16 | && (typeUtils.isBuiltinType(initType, node.initializer) || (initType.flags & ts.TypeFlags.NumberLike)) 17 | && !typeUtils.isPointerType(initType, node.initializer) 18 | && !typeUtils.isNullPointer(initType, node.initializer) 19 | ) { 20 | reportError(statement.currentFile, node, `type ${typeUtils.getBuiltinNameByType(initType) || 'number'} is not assignable to variable declaration of type ${typeUtils.getBuiltinNameByType(type)}`, error.TYPE_MISMATCH) 21 | } 22 | else if (typeUtils.isSizeType(type)) { 23 | return ts.visitNode(statement.context.factory.createVariableDeclaration( 24 | node.name, 25 | node.exclamationToken, 26 | node.type, 27 | nodeUtils.createPointerOperand(node.initializer) 28 | ), statement.visitor) 29 | } 30 | } 31 | const newNode = ts.visitEachChild(node, visitor, statement.context) 32 | 33 | statement.popStage() 34 | 35 | return newNode 36 | } 37 | -------------------------------------------------------------------------------- /src/std/string.ts: -------------------------------------------------------------------------------- 1 | import { mapUint8Array, memcpyFromUint8Array } from './memory' 2 | import { CTypeEnum } from '../typedef' 3 | import { CTypeEnumRead } from '../ctypeEnumRead' 4 | 5 | /** 6 | * 获取字符串长度,不包括字符串末尾的空字符(\0) 7 | * 8 | * @param pointer 9 | */ 10 | export function strlen(pointer: pointer) { 11 | let len = 0 12 | while (CTypeEnumRead[CTypeEnum.char](pointer++)) { 13 | len++ 14 | } 15 | return len 16 | } 17 | 18 | /** 19 | * 将一个字符串复制到另一个字符串 20 | * 21 | * @param destination 22 | * @param source 23 | */ 24 | export function strcpy(destination: pointer, source: pointer) { 25 | const len = strlen(source) + 1 26 | memcpyFromUint8Array(destination, len, mapUint8Array(source, len)) 27 | } 28 | 29 | /** 30 | * 将一个字符串追加到另一个字符串的末尾 31 | * 32 | * @param destination 33 | * @param source 34 | */ 35 | export function strcat(destination: pointer, source: pointer) { 36 | const len = strlen(source) + 1 37 | const len1 = strlen(destination) 38 | memcpyFromUint8Array(destination + len1, len, mapUint8Array(source, len)) 39 | } 40 | 41 | /** 42 | * 比较两个字符串的大小 43 | */ 44 | export function strcmp(str1: pointer, str2: pointer) { 45 | const len1 = strlen(str1) 46 | const len2 = strlen(str2) 47 | 48 | const len = Math.min(len1, len2) 49 | 50 | for (let i = 0; i < len; i++) { 51 | const char1 = CTypeEnumRead[CTypeEnum.char](str1 + i) 52 | const char2 = CTypeEnumRead[CTypeEnum.char](str2 + i) 53 | 54 | if (char1 > char2) { 55 | return 1 56 | } 57 | else if (char1 < char2) { 58 | return -1 59 | } 60 | } 61 | 62 | if (len1 > len2) { 63 | return 1 64 | } 65 | else if (len1 < len2) { 66 | return -1 67 | } 68 | else { 69 | return 0 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/defined.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('defined', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './defined_input.ts') 12 | output = path.join(distPath, './defined_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('let a = defined(boolean)', () => { 21 | const source = ` 22 | let b = defined(A) 23 | ` 24 | const target = ` 25 | let b = true 26 | ` 27 | check(source, target, { 28 | input, 29 | defined: { 30 | A: true 31 | } 32 | }) 33 | }) 34 | 35 | test('let a = defined(number)', () => { 36 | const source = ` 37 | let b = defined(A) 38 | ` 39 | const target = ` 40 | let b = 0 41 | ` 42 | check(source, target, { 43 | input, 44 | defined: { 45 | A: 0 46 | } 47 | }) 48 | }) 49 | 50 | test('let a = defined(string)', () => { 51 | const source = ` 52 | let b = defined(A) 53 | ` 54 | const target = ` 55 | let b = 'abc' 56 | ` 57 | check(source, target, { 58 | input, 59 | defined: { 60 | A: 'abc' 61 | } 62 | }) 63 | }) 64 | 65 | test('call(defined(x))', () => { 66 | const source = ` 67 | function a(a: number) { 68 | 69 | } 70 | a(defined(A)) 71 | ` 72 | const target = ` 73 | function a(a: number) { 74 | 75 | } 76 | a(0) 77 | ` 78 | check(source, target, { 79 | input, 80 | defined: { 81 | A: 0 82 | } 83 | }) 84 | }) 85 | }) -------------------------------------------------------------------------------- /src/transformer/__test__/case/assert.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('assert', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './assert_input.ts') 12 | output = path.join(distPath, './assert_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('assert condition', () => { 21 | const source = ` 22 | let a: uint8 23 | assert(a > 0) 24 | ` 25 | const target = ` 26 | let a: uint8; 27 | if (!(a > 0)) { 28 | console.error("[__test__cache/assert_input.ts line: 3]", "Assertion failed: a > 0"); 29 | debugger; 30 | } 31 | ` 32 | check(source, target, { 33 | input, 34 | defined: { 35 | DEBUG: true 36 | } 37 | }) 38 | }) 39 | 40 | test('assert condition msg', () => { 41 | const source = ` 42 | let a: uint8 43 | assert(a > 0, '123') 44 | ` 45 | const target = ` 46 | let a: uint8 47 | if (!(a > 0)) { 48 | console.error("[__test__cache/assert_input.ts line: 3]", "Assertion failed: a > 0", '123'); 49 | debugger; 50 | } 51 | ` 52 | check(source, target, { 53 | input, 54 | defined: { 55 | DEBUG: true 56 | } 57 | }) 58 | }) 59 | 60 | test('assert condition production', () => { 61 | const source = ` 62 | let a: uint8 63 | assert(a > 0) 64 | ` 65 | const target = ` 66 | let a: uint8 67 | ` 68 | check(source, target, { 69 | input, 70 | defined: { 71 | DEBUG: false 72 | } 73 | }) 74 | }) 75 | }) -------------------------------------------------------------------------------- /src/transformer/__test__/compare.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | 3 | // TypeScript 编译选项 4 | const compilerOptions: ts.CompilerOptions = { 5 | target: ts.ScriptTarget.ESNext, 6 | module: ts.ModuleKind.CommonJS 7 | } 8 | 9 | let list: ts.Node[] = [] 10 | 11 | function transformer(context: ts.TransformationContext) { 12 | return (node: ts.Node) => { 13 | function visitor(node: ts.Node) { 14 | list.push(node) 15 | return ts.visitEachChild(node, visitor, context) 16 | } 17 | return ts.visitNode(node, visitor) 18 | } 19 | } 20 | 21 | 22 | function each(node: ts.Node) { 23 | list = [] 24 | ts.transform(node, [transformer], compilerOptions) 25 | return list 26 | } 27 | 28 | function compareNode(node1: ts.Node, node2: ts.Node) { 29 | if (node1.kind !== node2.kind) { 30 | return false 31 | } 32 | if (ts.isIdentifier(node1) && ts.isIdentifier(node2) && node1.escapedText !== node2.escapedText) { 33 | return false 34 | } 35 | if (ts.isNumericLiteral(node1) && ts.isNumericLiteral(node2) && +node1.text !== +node2.text) { 36 | return false 37 | } 38 | if (ts.isStringLiteral(node1) && ts.isStringLiteral(node2) && node1.text !== node2.text) { 39 | return false 40 | } 41 | if (ts.isBigIntLiteral(node1) && ts.isBigIntLiteral(node2) && node1.text !== node2.text) { 42 | return false 43 | } 44 | return true 45 | } 46 | 47 | export default function compare(node1: ts.Node, node2: ts.Node) { 48 | const list1: ts.Node[] = each(node1) 49 | const list2: ts.Node[] = each(node2) 50 | 51 | if (list1.length === list2.length) { 52 | if (list1.length) { 53 | for (let i = 0; i < list1.length; i++) { 54 | if (!compareNode(list1[i], list2[i])) { 55 | return false 56 | } 57 | } 58 | } 59 | return true 60 | } 61 | 62 | return false 63 | } 64 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/bigint.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('bigint', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './bigint_input.ts') 12 | output = path.join(distPath, './bigint_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('bigint literal enable', () => { 21 | const source = ` 22 | let b = 0n 23 | ` 24 | const target = ` 25 | let b = 0n 26 | ` 27 | check(source, target, { 28 | input, 29 | defined: { 30 | BIGINT_LITERAL: true 31 | } 32 | }) 33 | }) 34 | 35 | test('bigint literal disable', () => { 36 | const source = ` 37 | let b = 1000n 38 | ` 39 | const target = ` 40 | let b = BigInt(1000) 41 | ` 42 | check(source, target, { 43 | input, 44 | defined: { 45 | BIGINT_LITERAL: false 46 | } 47 | }) 48 | }), 49 | 50 | test('bigint literal disable, expression', () => { 51 | const source = ` 52 | let b = 1000n * 123n * 345n 53 | ` 54 | const target = ` 55 | let b = BigInt(42435000) 56 | ` 57 | check(source, target, { 58 | input, 59 | defined: { 60 | BIGINT_LITERAL: false 61 | } 62 | }) 63 | }) 64 | 65 | test('bigint literal disable, expression max', () => { 66 | const source = ` 67 | let b = 9007199254740991n * 2n 68 | ` 69 | const target = ` 70 | let b = BigInt(9007199254740991) * BigInt(2) 71 | ` 72 | check(source, target, { 73 | input, 74 | defined: { 75 | BIGINT_LITERAL: false 76 | } 77 | }) 78 | }) 79 | }) -------------------------------------------------------------------------------- /src/ctypeEnumWrite.ts: -------------------------------------------------------------------------------- 1 | import type { CTypeEnum2Type } from './typedef' 2 | import { CTypeEnum } from './typedef' 3 | import { object } from '@libmedia/common' 4 | import unimplemented from './function/unimplemented' 5 | 6 | type CTypeEnumWrite = { 7 | [key in CTypeEnum]: (pointer: pointer, value: CTypeEnum2Type) => void 8 | } & { 9 | fill: (dst: pointer, value: uint8, size: size) => void, 10 | copy: (dst: pointer, src: pointer, size: size) => void, 11 | } 12 | export const CTypeEnumWrite: CTypeEnumWrite = { 13 | [CTypeEnum.char]: unimplemented, 14 | [CTypeEnum.atomic_char]: unimplemented, 15 | 16 | [CTypeEnum.uint8]: unimplemented, 17 | [CTypeEnum.atomic_uint8]: unimplemented, 18 | [CTypeEnum.uint16]: unimplemented, 19 | [CTypeEnum.atomic_uint16]: unimplemented, 20 | [CTypeEnum.uint32]: unimplemented, 21 | [CTypeEnum.atomic_uint32]: unimplemented, 22 | [CTypeEnum.uint64]: unimplemented, 23 | 24 | [CTypeEnum.int8]: unimplemented, 25 | [CTypeEnum.atomic_int8]: unimplemented, 26 | [CTypeEnum.int16]: unimplemented, 27 | [CTypeEnum.atomic_int16]: unimplemented, 28 | [CTypeEnum.int32]: unimplemented, 29 | [CTypeEnum.atomic_int32]: unimplemented, 30 | [CTypeEnum.int64]: unimplemented, 31 | 32 | [CTypeEnum.float]: unimplemented, 33 | [CTypeEnum.double]: unimplemented, 34 | [CTypeEnum.pointer]: unimplemented, 35 | 36 | [CTypeEnum.null]: unimplemented, 37 | [CTypeEnum.void]: unimplemented, 38 | 39 | [CTypeEnum.atomic_uint64]: unimplemented, 40 | [CTypeEnum.atomic_int64]: unimplemented, 41 | 42 | [CTypeEnum.bool]: unimplemented, 43 | [CTypeEnum.atomic_bool]: unimplemented, 44 | [CTypeEnum.size]: unimplemented, 45 | 'copy': unimplemented, 46 | 'fill': unimplemented 47 | } 48 | 49 | export function override(funcs: Partial) { 50 | object.extend(CTypeEnumWrite, funcs) 51 | } 52 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import typescript from '@rollup/plugin-typescript' 4 | import ts from 'typescript' 5 | 6 | let input = '' 7 | let output = '' 8 | let name = '' 9 | 10 | if (process.env.transformer) { 11 | input = 'src/transformer/index.ts' 12 | output = 'build' 13 | name = 'transformer' 14 | } 15 | else if (process.env.wasm_opt) { 16 | input = 'src/webassembly/wasm-opt.ts' 17 | output = 'build' 18 | name = 'wasm-opt' 19 | } 20 | 21 | function deDefined() { 22 | return (context) => { 23 | return (sourceFile) => { 24 | function visitor(node) { 25 | if (ts.isCallExpression(node) 26 | && ts.isIdentifier(node.expression) 27 | && node.expression.escapedText === 'defined' 28 | ) { 29 | return context.factory.createNumericLiteral(0) 30 | } 31 | return ts.visitEachChild(node, visitor, context) 32 | } 33 | return ts.visitNode(sourceFile, visitor) 34 | } 35 | } 36 | } 37 | 38 | export default defineConfig({ 39 | input, 40 | external: ['typescript', 'path', 'commander', 'child_process', 'fs', 'os', 'url', '@libmedia/common'], 41 | output: [ 42 | { 43 | dir: output, 44 | format: 'cjs', 45 | exports: 'auto', 46 | sourcemap: true, 47 | entryFileNames: name + '.cjs' 48 | }, 49 | { 50 | dir: output, 51 | format: 'es', 52 | sourcemap: true, 53 | entryFileNames: name + '.mjs' 54 | } 55 | ], 56 | plugins: [ 57 | resolve(), 58 | typescript({ 59 | tsconfig: './tsconfig.build.json', 60 | noCheck: true, 61 | transformers: { 62 | before: [ 63 | { 64 | type: 'program', 65 | factory: (program) => { 66 | return deDefined() 67 | } 68 | } 69 | ] 70 | } 71 | }) 72 | ], 73 | treeshake: true 74 | }) 75 | -------------------------------------------------------------------------------- /src/asm/memory.asm.ts: -------------------------------------------------------------------------------- 1 | export default asm` 2 | (func $write8 (export "write8") (param $p0 i32) (param $p1 i32) 3 | (local.get $p0) 4 | (local.get $p1) 5 | (i32.store8) 6 | ) 7 | (func $write16 (export "write16") (param $p0 i32) (param $p1 i32) 8 | (local.get $p0) 9 | (local.get $p1) 10 | (i32.store16) 11 | ) 12 | (func $write32 (export "write32") (param $p0 i32) (param $p1 i32) 13 | (local.get $p0) 14 | (local.get $p1) 15 | (i32.store) 16 | ) 17 | (func $write64 (export "write64") (param $p0 i32) (param $p1 i64) 18 | (local.get $p0) 19 | (local.get $p1) 20 | (i64.store) 21 | ) 22 | (func $writef32 (export "writef32") (param $p0 i32) (param $p1 f32) 23 | (local.get $p0) 24 | (local.get $p1) 25 | (f32.store) 26 | ) 27 | (func $writef64 (export "writef64")(param $p0 i32) (param $p1 f64) 28 | (local.get $p0) 29 | (local.get $p1) 30 | (f64.store) 31 | ) 32 | (func $read8 (export "read8") (param $p0 i32) (result i32) 33 | (local.get $p0) 34 | (i32.load8_s) 35 | ) 36 | (func $readU8 (export "readU8") (param $p0 i32) (result i32) 37 | (local.get $p0) 38 | (i32.load8_u) 39 | ) 40 | (func $read16 (export "read16") (param $p0 i32) (result i32) 41 | (local.get $p0) 42 | (i32.load16_s) 43 | ) 44 | (func $readU16 (export "readU16") (param $p0 i32) (result i32) 45 | (local.get $p0) 46 | (i32.load16_u) 47 | ) 48 | (func $read32 (export "read32") (param $p0 i32) (result i32) 49 | (local.get $p0) 50 | (i32.load) 51 | ) 52 | (func $read64 (export "read64") (param $p0 i32) (result i64) 53 | (local.get $p0) 54 | (i64.load) 55 | ) 56 | (func $readf32 (export "readf32") (param $p0 i32) (result f32) 57 | (local.get $p0) 58 | (f32.load) 59 | ) 60 | (func $readf64 (export "readf64") (param $p0 i32) (result f64) 61 | (local.get $p0) 62 | (f64.load) 63 | ) 64 | (func $fill (export "fill") (param $p0 i32) (param $p1 i32) (param $p2 i32) 65 | (local.get $p0) 66 | (local.get $p1) 67 | (local.get $p2) 68 | (memory.fill) 69 | ) 70 | (func $copy (export "copy") (param $p0 i32) (param $p1 i32) (param $p2 i32) 71 | (local.get $p0) 72 | (local.get $p1) 73 | (local.get $p2) 74 | (memory.copy) 75 | ) 76 | ` 77 | -------------------------------------------------------------------------------- /src/asm/memory64.asm.ts: -------------------------------------------------------------------------------- 1 | export default asm64` 2 | (func $write8 (export "write8") (param $p0 i64) (param $p1 i32) 3 | (local.get $p0) 4 | (local.get $p1) 5 | (i32.store8) 6 | ) 7 | (func $write16 (export "write16") (param $p0 i64) (param $p1 i32) 8 | (local.get $p0) 9 | (local.get $p1) 10 | (i32.store16) 11 | ) 12 | (func $write32 (export "write32") (param $p0 i64) (param $p1 i32) 13 | (local.get $p0) 14 | (local.get $p1) 15 | (i32.store) 16 | ) 17 | (func $write64 (export "write64") (param $p0 i64) (param $p1 i64) 18 | (local.get $p0) 19 | (local.get $p1) 20 | (i64.store) 21 | ) 22 | (func $writef32 (export "writef32") (param $p0 i64) (param $p1 f32) 23 | (local.get $p0) 24 | (local.get $p1) 25 | (f32.store) 26 | ) 27 | (func $writef64 (export "writef64")(param $p0 i64) (param $p1 f64) 28 | (local.get $p0) 29 | (local.get $p1) 30 | (f64.store) 31 | ) 32 | (func $read8 (export "read8") (param $p0 i64) (result i32) 33 | (local.get $p0) 34 | (i32.load8_s) 35 | ) 36 | (func $readU8 (export "readU8") (param $p0 i64) (result i32) 37 | (local.get $p0) 38 | (i32.load8_u) 39 | ) 40 | (func $read16 (export "read16") (param $p0 i64) (result i32) 41 | (local.get $p0) 42 | (i32.load16_s) 43 | ) 44 | (func $readU16 (export "readU16") (param $p0 i64) (result i32) 45 | (local.get $p0) 46 | (i32.load16_u) 47 | ) 48 | (func $read32 (export "read32") (param $p0 i64) (result i32) 49 | (local.get $p0) 50 | (i32.load) 51 | ) 52 | (func $read64 (export "read64") (param $p0 i64) (result i64) 53 | (local.get $p0) 54 | (i64.load) 55 | ) 56 | (func $readf32 (export "readf32") (param $p0 i64) (result f32) 57 | (local.get $p0) 58 | (f32.load) 59 | ) 60 | (func $readf64 (export "readf64") (param $p0 i64) (result f64) 61 | (local.get $p0) 62 | (f64.load) 63 | ) 64 | (func $fill (export "fill") (param $p0 i64) (param $p1 i32) (param $p2 i64) 65 | (local.get $p0) 66 | (local.get $p1) 67 | (local.get $p2) 68 | (memory.fill) 69 | ) 70 | (func $copy (export "copy") (param $p0 i64) (param $p1 i64) (param $p2 i64) 71 | (local.get $p0) 72 | (local.get $p1) 73 | (local.get $p2) 74 | (memory.copy) 75 | ) 76 | ` 77 | -------------------------------------------------------------------------------- /src/transformer/visitor/blockVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import { array } from '@libmedia/common' 4 | import statement, { BlockType } from '../statement' 5 | import * as nodeUtils from '../util/nodeutil' 6 | 7 | export default function (node: ts.Block, visitor: ts.Visitor): ts.Node { 8 | 9 | let type = BlockType.UNKNOWN 10 | 11 | if (node.parent) { 12 | if (ts.isFunctionDeclaration(node.parent) 13 | || ts.isFunctionExpression(node.parent) 14 | || ts.isArrowFunction(node.parent) 15 | || ts.isMethodDeclaration(node.parent) 16 | ) { 17 | type = BlockType.FUNCTION 18 | } 19 | else if (ts.isIfStatement(node.parent)) { 20 | type = BlockType.IF 21 | } 22 | else if (ts.isForStatement(node.parent) || ts.isForOfStatement(node.parent) || ts.isWhileStatement(node.parent)) { 23 | type = BlockType.LOOP 24 | } 25 | } 26 | 27 | statement.pushStack(type) 28 | 29 | if (statement.cheapCompilerOptions.defined.ENABLE_SYNCHRONIZE_API 30 | && node.parent 31 | && (ts.isFunctionDeclaration(node.parent) 32 | || ts.isFunctionExpression(node.parent) 33 | || ts.isArrowFunction(node.parent) 34 | || ts.isMethodDeclaration(node.parent) 35 | ) 36 | && nodeUtils.isSynchronizeFunction(node.parent) 37 | ) { 38 | statement.getCurrentStack().synchronize = true 39 | } 40 | 41 | let nodes = ts.visitEachChild(node, visitor, statement.context) 42 | 43 | const stack = statement.getCurrentStack() 44 | 45 | const updatedStatements = [] 46 | array.each(stack.topDeclaration, (item) => { 47 | updatedStatements.push(statement.context.factory.createVariableStatement( 48 | undefined, 49 | statement.context.factory.createVariableDeclarationList([ 50 | statement.context.factory.createVariableDeclaration(item.formatName, undefined, undefined, item.initializer) 51 | ]) 52 | )) 53 | }) 54 | 55 | if (updatedStatements.length) { 56 | nodes = statement.context.factory.createBlock( 57 | [...updatedStatements, ...nodes.statements], 58 | true 59 | ) 60 | } 61 | 62 | statement.popStack() 63 | 64 | return nodes 65 | } 66 | -------------------------------------------------------------------------------- /src/transformer/visitor/expressionStatementVisitor.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | import * as constant from '../constant' 3 | import statement from '../statement' 4 | 5 | export default function (node: ts.ExpressionStatement, visitor: ts.Visitor): ts.Node { 6 | 7 | if (ts.isCallExpression(node.expression) 8 | && ts.isIdentifier(node.expression.expression) 9 | && node.expression.expression.escapedText === constant.assert 10 | ) { 11 | if (statement.cheapCompilerOptions.defined['DEBUG'] && node.expression.arguments.length >= 1) { 12 | const newNode = ts.visitEachChild(node.expression, visitor, statement.context) 13 | 14 | const list: ts.Statement[] = [] 15 | 16 | const { line } = ts.getLineAndCharacterOfPosition(statement.currentFile, node.getStart()) 17 | 18 | const args: ts.Expression[] = [ 19 | statement.context.factory.createStringLiteral(`[${statement.currentFilePath} line: ${line + 1}]`), 20 | statement.context.factory.createStringLiteral(`Assertion failed: ${node.expression.arguments[0].getText()}`) 21 | ] 22 | 23 | if (newNode.arguments[1]) { 24 | args.push(newNode.arguments[1]) 25 | } 26 | 27 | list.push(statement.context.factory.createExpressionStatement(statement.context.factory.createCallExpression( 28 | statement.context.factory.createPropertyAccessExpression( 29 | statement.context.factory.createIdentifier('console'), 30 | statement.context.factory.createIdentifier('error') 31 | ), 32 | undefined, 33 | args 34 | ))) 35 | 36 | list.push(statement.context.factory.createDebuggerStatement()) 37 | 38 | return statement.context.factory.createIfStatement( 39 | statement.context.factory.createPrefixUnaryExpression( 40 | ts.SyntaxKind.ExclamationToken, 41 | statement.context.factory.createParenthesizedExpression(newNode.arguments[0]) 42 | ), 43 | statement.context.factory.createBlock( 44 | list, 45 | true 46 | ) 47 | ) 48 | } 49 | else { 50 | return undefined 51 | } 52 | } 53 | 54 | return ts.visitEachChild(node, visitor, statement.context) 55 | } 56 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/reinterpretCast.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('reinterpretCast', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './reinterpret_cast_input.ts') 12 | output = path.join(distPath, './reinterpret_cast_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('reinterpret_cast>(pointer)', () => { 21 | const source = ` 22 | let a: pointer 23 | let b = reinterpret_cast>(a) 24 | ` 25 | const target = ` 26 | let a: pointer 27 | let b = a 28 | ` 29 | check(source, target, { 30 | input 31 | }) 32 | }) 33 | 34 | test('reinterpret_cast(sizeof(a))', () => { 35 | const source = ` 36 | let a: int32 37 | let b = reinterpret_cast(sizeof(a)) 38 | ` 39 | const target = ` 40 | let a: int32 41 | let b = 4 42 | ` 43 | check(source, target, { 44 | input 45 | }) 46 | }) 47 | 48 | test('reinterpret_cast(sizeof(a)) wasm64', () => { 49 | const source = ` 50 | let a: int32 51 | let b = reinterpret_cast(sizeof(a)) 52 | ` 53 | const target = ` 54 | let a: int32 55 | let b = 4 56 | ` 57 | check(source, target, { 58 | input 59 | }) 60 | }) 61 | 62 | 63 | test('reinterpret_cast(size)', () => { 64 | const source = ` 65 | let a: size 66 | let b = reinterpret_cast(a) 67 | ` 68 | const target = ` 69 | let a: size 70 | let b = a 71 | ` 72 | check(source, target, { 73 | input 74 | }) 75 | }) 76 | 77 | 78 | test('reinterpret_cast(size) wasm', () => { 79 | const source = ` 80 | let a: size 81 | let b = reinterpret_cast(a) 82 | ` 83 | const target = ` 84 | let a: size 85 | let b = Number(a) 86 | ` 87 | check(source, target, { 88 | input, 89 | defined: { 90 | WASM_64: true 91 | } 92 | }) 93 | }) 94 | }) -------------------------------------------------------------------------------- /src/transformer/visitor/expressionVisitor.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | import statement from '../statement' 3 | import * as nodeUtils from '../util/nodeutil' 4 | 5 | import callVisitor from './callVisitor' 6 | import propertyAccessExpressionVisitor from './propertyAccessExpressionVisitor' 7 | import binaryExpressionVisitor from './binaryExpressionVisitor' 8 | import unaryExpressionVisitor from './unaryExpressionVisitor' 9 | import elementAccessExpressionVisitor from './elementAccessExpressionVisitor' 10 | import taggedTemplateExpressionVisitor from './taggedTemplateExpressionVisitor' 11 | import conditionalExpressionVisitor from './conditionalExpressionVisitor' 12 | 13 | export default function (node: ts.Expression, visitor: ts.Visitor): ts.Node { 14 | if (ts.isBinaryExpression(node)) { 15 | return binaryExpressionVisitor(node, visitor) 16 | } 17 | else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) { 18 | return unaryExpressionVisitor(node, visitor) 19 | } 20 | else if (ts.isCallExpression(node)) { 21 | return callVisitor(node, visitor) 22 | } 23 | else if (ts.isPropertyAccessExpression(node)) { 24 | return propertyAccessExpressionVisitor(node, visitor) 25 | } 26 | else if (ts.isElementAccessExpression(node)) { 27 | return elementAccessExpressionVisitor(node, visitor) 28 | } 29 | else if (statement.cheapCompilerOptions.defined.ENABLE_SYNCHRONIZE_API 30 | && ts.isAwaitExpression(node) 31 | && node.expression 32 | && ts.isCallExpression(node.expression) 33 | && (statement.lookupSynchronized() 34 | || nodeUtils.isSynchronizeFunction(statement.typeChecker.getSymbolAtLocation(ts.isPropertyAccessExpression(node.expression.expression) 35 | ? node.expression.expression.name 36 | : node.expression.expression)?.valueDeclaration as ts.FunctionDeclaration) 37 | ) 38 | ) { 39 | return ts.visitEachChild(node.expression, visitor, statement.context) 40 | } 41 | else if (ts.isTaggedTemplateExpression(node)) { 42 | return taggedTemplateExpressionVisitor(node, visitor) 43 | } 44 | else if (ts.isConditionalExpression(node)) { 45 | return conditionalExpressionVisitor(node, visitor) 46 | } 47 | 48 | return ts.visitEachChild(node, visitor, statement.context) 49 | } 50 | -------------------------------------------------------------------------------- /src/thread/initFunction.ts: -------------------------------------------------------------------------------- 1 | import { SELF } from '@libmedia/common/constant' 2 | if (defined(ENV_NODE) && !defined(ENV_CJS)) { 3 | // @ts-ignore 4 | import { parentPort as parentPort_ } from 'worker_threads' 5 | } 6 | 7 | let parentPort = SELF 8 | if (defined(ENV_NODE)) { 9 | if (defined(ENV_CJS)) { 10 | const { parentPort: parentPort_ } = require('worker_threads') 11 | parentPort = parentPort_ 12 | } 13 | else { 14 | parentPort = parentPort_ 15 | } 16 | } 17 | 18 | export default function init(run: (...args: any[]) => any) { 19 | let retval: any 20 | 21 | const handler = (message: MessageEvent) => { 22 | const origin = defined(ENV_NODE) ? message : message.data 23 | const type = origin.type 24 | const data = origin.data 25 | 26 | switch (type) { 27 | case 'init': 28 | if (SELF.CHeap && SELF.CHeap.initThread && SELF.CHeap.Config.USE_THREADS) { 29 | SELF.CHeap.initThread(data).then(() => { 30 | parentPort.postMessage({ 31 | type: 'ready' 32 | }) 33 | }) 34 | return 35 | } 36 | 37 | parentPort.postMessage({ 38 | type: 'ready' 39 | }) 40 | break 41 | case 'run': 42 | retval = run(data.params) 43 | break 44 | case 'stop': 45 | if (retval && retval.then) { 46 | retval.then((res: any) => { 47 | // @ts-ignore 48 | if (SELF.__freeSmartPtr__) { 49 | // @ts-ignore 50 | SELF.__freeSmartPtr__() 51 | } 52 | parentPort.postMessage({ 53 | type: 'stopped', 54 | data: res 55 | }) 56 | }) 57 | } 58 | else { 59 | // @ts-ignore 60 | if (SELF.__freeSmartPtr__) { 61 | // @ts-ignore 62 | SELF.__freeSmartPtr__() 63 | } 64 | parentPort.postMessage({ 65 | type: 'stopped', 66 | data: retval 67 | }) 68 | } 69 | break 70 | default: 71 | break 72 | } 73 | } 74 | 75 | if (defined(ENV_NODE)) { 76 | // @ts-ignore 77 | parentPort.on('message', handler) 78 | } 79 | else { 80 | parentPort.onmessage = handler 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/webassembly/runtime/asm/libc.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 3 | import asm from './libc.asm' 4 | import asm64 from './libc64.asm' 5 | import { BuiltinTableSlot } from '../../../allocator/Table' 6 | import { Table } from '../../../heap' 7 | 8 | import { base64, wasm as wasmUtils, logger } from '@libmedia/common' 9 | 10 | /** 11 | * WebAssembly runtime 实例 12 | */ 13 | export let wasmThreadProxy: WebAssembly.Instance 14 | 15 | let support = true 16 | 17 | export function isSupport() { 18 | return support 19 | } 20 | 21 | export function init(memory: WebAssembly.Memory, initial: uint32, maximum: uint32) { 22 | try { 23 | const wasm = wasmUtils.setMemoryMeta(base64.base64ToUint8Array(defined(WASM_64) ? asm64 : asm), { 24 | shared: typeof SharedArrayBuffer === 'function' && memory.buffer instanceof SharedArrayBuffer, 25 | initial, 26 | maximum 27 | }) 28 | 29 | wasmThreadProxy = new WebAssembly.Instance(new WebAssembly.Module(wasm as BufferSource), { 30 | env: { 31 | memory, 32 | malloc: function (size: size) { 33 | return malloc(size) 34 | }, 35 | calloc: function (num: size, size: size) { 36 | return calloc(num, size) 37 | }, 38 | realloc: function (pointer: pointer, size: size) { 39 | return realloc(pointer, size) 40 | }, 41 | aligned_alloc(alignment: size, size: size): pointer { 42 | return aligned_alloc(alignment, size) 43 | }, 44 | free: function (pointer: pointer) { 45 | free(pointer) 46 | } 47 | } 48 | }) 49 | 50 | Table.set(static_cast>(BuiltinTableSlot.MALLOC as uint32), wasmThreadProxy.exports.malloc as any) 51 | Table.set(static_cast>(BuiltinTableSlot.FREE as uint32), wasmThreadProxy.exports.free as any) 52 | Table.set(static_cast>(BuiltinTableSlot.CALLOC as uint32), wasmThreadProxy.exports.calloc as any) 53 | Table.set(static_cast>(BuiltinTableSlot.REALLOC as uint32), wasmThreadProxy.exports.realloc as any) 54 | Table.set(static_cast>(BuiltinTableSlot.ALIGNED_ALLOC as uint32), wasmThreadProxy.exports.alignedAlloc as any) 55 | } 56 | catch (error) { 57 | support = false 58 | logger.warn('libc asm not support, cannot use asm thread function') 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/parameter.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath, transform2AST } from '../transformer' 4 | 5 | describe('parameter', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './parameter_input.ts') 12 | output = path.join(distPath, './parameter_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('int32 assignable to pointer', () => { 21 | const source = ` 22 | function test(a: pointer) { 23 | } 24 | 25 | test(4 as int32) 26 | ` 27 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 28 | 29 | transform2AST(source, { 30 | input 31 | }) 32 | 33 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/type int32 is not assignable to parameter of type pointer\n?$/)) 34 | consoleErrorSpy.mockRestore() 35 | }) 36 | 37 | test('pointer assignable to int32', () => { 38 | const source = ` 39 | function test(a: int32) { 40 | } 41 | let a: pointer 42 | test(a) 43 | ` 44 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 45 | 46 | transform2AST(source, { 47 | input 48 | }) 49 | 50 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/type pointer is not assignable to parameter of type int32\n?$/)) 51 | consoleErrorSpy.mockRestore() 52 | }) 53 | 54 | test('size assignable to int32 wasm64', () => { 55 | const source = ` 56 | function test(a: size) { 57 | } 58 | let a: int32 59 | test(a) 60 | ` 61 | 62 | const target = ` 63 | function test(a: size) { 64 | } 65 | let a: int32 66 | test(BigInt(a)) 67 | ` 68 | check(source, target, { 69 | input, 70 | defined: { 71 | WASM_64: true 72 | } 73 | }) 74 | }) 75 | 76 | test('size assignable to number wasm64', () => { 77 | const source = ` 78 | function test(a: size) { 79 | } 80 | test(12) 81 | ` 82 | 83 | const target = ` 84 | function test(a: size) { 85 | } 86 | test(12n) 87 | ` 88 | check(source, target, { 89 | input, 90 | defined: { 91 | WASM_64: true 92 | } 93 | }) 94 | }) 95 | }) -------------------------------------------------------------------------------- /src/transformer/__test__/case/memory.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | import { allocatorImport } from './snippet' 5 | 6 | describe('identifier', () => { 7 | 8 | let input: string 9 | let output: string 10 | 11 | beforeAll(() => { 12 | input = path.join(distPath, './identifier_input.ts') 13 | output = path.join(distPath, './identifier_output.ts') 14 | }) 15 | 16 | afterAll(() => { 17 | fs.unlinkSync(input) 18 | fs.rmSync(output, { force: true }) 19 | }) 20 | 21 | test('malloc', () => { 22 | const source = ` 23 | let b = malloc(4) 24 | ` 25 | const target = ` 26 | ${allocatorImport} 27 | let b = Allocator.malloc(4) 28 | ` 29 | check(source, target, { 30 | input 31 | }) 32 | }) 33 | 34 | test('malloc local', () => { 35 | const source = ` 36 | function malloc(a: number) { 37 | 38 | } 39 | let b = malloc(4) 40 | ` 41 | const target = ` 42 | function malloc(a: number) { 43 | 44 | } 45 | let b = malloc(4) 46 | ` 47 | check(source, target, { 48 | input 49 | }) 50 | }) 51 | 52 | test('calloc', () => { 53 | const source = ` 54 | let b = calloc(4, 4) 55 | ` 56 | const target = ` 57 | ${allocatorImport} 58 | let b = Allocator.calloc(4, 4) 59 | ` 60 | check(source, target, { 61 | input 62 | }) 63 | }) 64 | 65 | test('realloc', () => { 66 | const source = ` 67 | let p: pointer 68 | let b = realloc(p, 4) 69 | ` 70 | const target = ` 71 | ${allocatorImport} 72 | let p: pointer 73 | let b = Allocator.realloc(p, 4) 74 | ` 75 | check(source, target, { 76 | input 77 | }) 78 | }) 79 | 80 | test('aligned_alloc', () => { 81 | const source = ` 82 | let b = aligned_alloc(8, 4) 83 | ` 84 | const target = ` 85 | ${allocatorImport} 86 | let b = Allocator.alignedAlloc(8, 4) 87 | ` 88 | check(source, target, { 89 | input 90 | }) 91 | }) 92 | 93 | test('free', () => { 94 | const source = ` 95 | let p: pointer 96 | free(p) 97 | ` 98 | const target = ` 99 | ${allocatorImport} 100 | let p: pointer 101 | Allocator.free(p) 102 | ` 103 | check(source, target, { 104 | input 105 | }) 106 | }) 107 | }) -------------------------------------------------------------------------------- /src/transformer/__test__/case/make.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath, transform2AST } from '../transformer' 4 | import { makeImport, symbolImport, definedMetaPropertyImport } from './snippet' 5 | 6 | describe('make', () => { 7 | 8 | let input: string 9 | let output: string 10 | 11 | beforeAll(() => { 12 | input = path.join(distPath, './make.ts') 13 | output = path.join(distPath, './make_output.ts') 14 | }) 15 | 16 | afterAll(() => { 17 | fs.unlinkSync(input) 18 | fs.rmSync(output, { force: true }) 19 | }) 20 | 21 | test('make', () => { 22 | const source = ` 23 | @struct 24 | class TestA { 25 | a: int8 26 | } 27 | let b = make() 28 | ` 29 | const target = ` 30 | ${symbolImport} 31 | ${definedMetaPropertyImport} 32 | ${makeImport} 33 | class TestA { 34 | a: int8 35 | static { 36 | const prototype = this.prototype; 37 | const map = new Map(); 38 | map.set("a", { 0: 11, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 }) 39 | definedMetaProperty(prototype, symbolStruct, true) 40 | definedMetaProperty(prototype, symbolStructMaxBaseTypeByteLength, 1) 41 | definedMetaProperty(prototype, symbolStructLength, 1) 42 | definedMetaProperty(prototype, symbolStructKeysMeta, map) 43 | } 44 | } 45 | let b = make(TestA) 46 | ` 47 | check(source, target, { 48 | input 49 | }) 50 | }) 51 | 52 | test('make no T', () => { 53 | const source = ` 54 | @struct 55 | class TestA { 56 | a: int8 57 | } 58 | let b = make() 59 | ` 60 | 61 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 62 | 63 | transform2AST(source, { 64 | input 65 | }) 66 | 67 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/invalid typeArguments\n?$/)) 68 | consoleErrorSpy.mockRestore() 69 | }) 70 | 71 | test('make not struct', () => { 72 | const source = ` 73 | class TestA { 74 | a: int8 75 | } 76 | let b = make() 77 | ` 78 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 79 | 80 | transform2AST(source, { 81 | input 82 | }) 83 | 84 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/invalid typeArguments, not found struct defined of TestA\n?$/)) 85 | consoleErrorSpy.mockRestore() 86 | }) 87 | }) -------------------------------------------------------------------------------- /src/transformer/__test__/case/identifier.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | import { CTypeEnum } from '../../../typedef' 5 | 6 | describe('identifier', () => { 7 | 8 | let input: string 9 | let output: string 10 | 11 | beforeAll(() => { 12 | input = path.join(distPath, './identifier_input.ts') 13 | output = path.join(distPath, './identifier_output.ts') 14 | }) 15 | 16 | afterAll(() => { 17 | fs.unlinkSync(input) 18 | fs.rmSync(output, { force: true }) 19 | }) 20 | 21 | test('let a = nullptr', () => { 22 | const source = ` 23 | let b = nullptr 24 | ` 25 | const target = ` 26 | let b = ${CTypeEnum.null} 27 | ` 28 | check(source, target, { 29 | input 30 | }) 31 | }) 32 | 33 | test('switch case', () => { 34 | const source = ` 35 | function a(a: any){ 36 | switch(a) { 37 | case uint8: 38 | break 39 | case char: 40 | break 41 | } 42 | } 43 | ` 44 | const target = ` 45 | function a(a: any){ 46 | switch(a) { 47 | case ${CTypeEnum.uint8}: 48 | break 49 | case ${CTypeEnum.char}: 50 | break 51 | } 52 | } 53 | ` 54 | check(source, target, { 55 | input 56 | }) 57 | }) 58 | 59 | test('return case', () => { 60 | const source = ` 61 | function a(a: any){ 62 | return nullptr 63 | } 64 | ` 65 | const target = ` 66 | function a(a: any){ 67 | return 0 68 | } 69 | ` 70 | check(source, target, { 71 | input 72 | }) 73 | }) 74 | 75 | test('local let', () => { 76 | const source = ` 77 | function a(a: any){ 78 | let nullptr = 8 79 | return nullptr 80 | } 81 | ` 82 | const target = ` 83 | function a(a: any){ 84 | let nullptr = 8 85 | return nullptr 86 | } 87 | ` 88 | check(source, target, { 89 | input 90 | }) 91 | }) 92 | 93 | test('global let', () => { 94 | const source = ` 95 | let nullptr = 8 96 | function a(a: any){ 97 | return nullptr 98 | } 99 | ` 100 | const target = ` 101 | let nullptr = 8 102 | function a(a: any){ 103 | return nullptr 104 | } 105 | ` 106 | check(source, target, { 107 | input 108 | }) 109 | }) 110 | }) -------------------------------------------------------------------------------- /src/transformer/visitor/conditionalExpressionVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as nodeUtil from '../util/nodeutil' 5 | import * as typeUtil from '../util/typeutil' 6 | 7 | export default function (node: ts.ConditionalExpression, visitor: ts.Visitor): ts.Node { 8 | if (ts.visitNode(node.condition, nodeUtil.hasDefined) && ts.visitNode(node.condition, nodeUtil.checkConditionCompile)) { 9 | if (nodeUtil.checkBool(node.condition, visitor)) { 10 | if (statement.cheapCompilerOptions.defined.WASM_64) { 11 | const type = statement.typeChecker.getTypeAtLocation(node.whenFalse) 12 | if (typeUtil.isSizeType(type)) { 13 | return nodeUtil.createPointerOperand(ts.visitNode(node.whenTrue, visitor) as ts.Expression) 14 | } 15 | } 16 | return ts.visitNode(node.whenTrue, visitor) 17 | } 18 | else { 19 | if (statement.cheapCompilerOptions.defined.WASM_64) { 20 | const type = statement.typeChecker.getTypeAtLocation(node.whenTrue) 21 | if (typeUtil.isSizeType(type)) { 22 | return nodeUtil.createPointerOperand(ts.visitNode(node.whenFalse, visitor) as ts.Expression) 23 | } 24 | } 25 | return ts.visitNode(node.whenFalse, visitor) 26 | } 27 | } 28 | if (statement.cheapCompilerOptions.defined.WASM_64) { 29 | const type1 = statement.typeChecker.getTypeAtLocation(node.whenTrue) 30 | const type2 = statement.typeChecker.getTypeAtLocation(node.whenFalse) 31 | if (typeUtil.isSizeType(type1) && !typeUtil.isSizeType(type2)) { 32 | return statement.context.factory.createConditionalExpression( 33 | ts.visitNode(node.condition, statement.visitor) as ts.Expression, 34 | node.questionToken, 35 | ts.visitNode(node.whenTrue, statement.visitor) as ts.Expression, 36 | node.colonToken, 37 | nodeUtil.createPointerOperand(ts.visitNode(node.whenFalse, statement.visitor) as ts.Expression) 38 | ) 39 | } 40 | else if (!typeUtil.isSizeType(type1) && typeUtil.isSizeType(type2)) { 41 | return statement.context.factory.createConditionalExpression( 42 | ts.visitNode(node.condition, statement.visitor) as ts.Expression, 43 | node.questionToken, 44 | nodeUtil.createPointerOperand(ts.visitNode(node.whenTrue, statement.visitor) as ts.Expression), 45 | node.colonToken, 46 | ts.visitNode(node.whenFalse, statement.visitor) as ts.Expression 47 | ) 48 | } 49 | } 50 | return ts.visitEachChild(node, visitor, statement.context) 51 | } 52 | -------------------------------------------------------------------------------- /src/transformer/visitor/classDeclarationVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement from '../statement' 4 | import * as constant from '../constant' 5 | import * as typeUtils from '../util/typeutil' 6 | import generateStruct from './function/generateStruct' 7 | 8 | export default function (node: ts.ClassDeclaration, visitor: ts.Visitor): ts.Node[] | ts.Node { 9 | const type = statement.typeChecker.getTypeAtLocation(node) 10 | const struct = typeUtils.getStructByType(type) 11 | if (struct && (!node.modifiers || !node.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword))) { 12 | 13 | const structName = node.name.escapedText as string 14 | 15 | if (!statement.hasStruct(structName)) { 16 | statement.addStruct(structName) 17 | } 18 | 19 | const structNode = ts.visitEachChild(node, visitor, statement.context) 20 | 21 | const newNode: ts.Node[] = [ 22 | statement.context.factory.createClassDeclaration( 23 | structNode.modifiers, 24 | structNode.name, 25 | structNode.typeParameters, 26 | structNode.heritageClauses, 27 | [ 28 | ...structNode.members, 29 | statement.context.factory.createClassStaticBlockDeclaration(statement.context.factory.createBlock([ 30 | statement.context.factory.createVariableStatement( 31 | undefined, 32 | statement.context.factory.createVariableDeclarationList([ 33 | statement.context.factory.createVariableDeclaration( 34 | statement.context.factory.createIdentifier(constant.prototype), 35 | undefined, 36 | undefined, 37 | statement.context.factory.createPropertyAccessExpression( 38 | statement.context.factory.createThis(), 39 | statement.context.factory.createIdentifier(constant.prototype) 40 | ) 41 | ) 42 | ], ts.NodeFlags.Const) 43 | ), 44 | ...generateStruct(struct) 45 | ], true)) 46 | ] 47 | ) 48 | ] 49 | 50 | const item = statement.getDeclaration(structName) 51 | 52 | if (item) { 53 | newNode.push(statement.context.factory.createExpressionStatement(statement.context.factory.createBinaryExpression( 54 | statement.context.factory.createIdentifier(item.formatName), 55 | ts.SyntaxKind.EqualsToken, 56 | statement.context.factory.createIdentifier(item.name) 57 | ))) 58 | } 59 | return newNode 60 | } 61 | 62 | return ts.visitEachChild(node, visitor, statement.context) 63 | } 64 | -------------------------------------------------------------------------------- /src/transformer/__test__/transformer.ts: -------------------------------------------------------------------------------- 1 | 2 | import './defined' 3 | import ts from 'typescript' 4 | import { before } from '../index' 5 | import * as path from 'path' 6 | import * as fs from 'fs' 7 | import compare from './compare' 8 | import os from 'os' 9 | 10 | export const projectPath = __dirname 11 | export const distPath = path.join(__dirname, './__test__cache') 12 | const cheapdef = path.join(__dirname, '../../cheapdef.ts') 13 | 14 | if (!fs.existsSync(distPath)) { 15 | fs.mkdirSync(distPath, { recursive: true }) 16 | } 17 | 18 | // TypeScript 编译选项 19 | const compilerOptions: ts.CompilerOptions = { 20 | target: ts.ScriptTarget.ESNext, 21 | module: ts.ModuleKind.ESNext, 22 | outDir: distPath 23 | } 24 | 25 | // 创建 TypeScript 编译器实例 26 | const compilerHost = ts.createCompilerHost(compilerOptions) 27 | const printer = ts.createPrinter() 28 | 29 | function generateAST(source: string) { 30 | const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest) 31 | return sourceFile 32 | } 33 | 34 | export function transform2AST(source: string, options: { 35 | input: string 36 | output?: string 37 | defined?: Record 38 | include?: string[] 39 | }) { 40 | fs.writeFileSync(options.input, source) 41 | const program = ts.createProgram([cheapdef, options.input].concat(options.include ? options.include : []), compilerOptions, compilerHost) 42 | 43 | let wat2wasmPath = path.resolve(__dirname, '../../../build/asm/ubuntu') + '/wat2wasm' 44 | if (os.platform() === 'win32') { 45 | wat2wasmPath = path.resolve(__dirname, '../../../build/asm/win') + '/wat2wasm.exe' 46 | } 47 | else if (os.platform() === 'darwin') { 48 | wat2wasmPath = path.resolve(__dirname, '../../../build/asm/macos') + '/wat2wasm' 49 | } 50 | 51 | const transformerBefore = before(program, { 52 | projectPath: projectPath, 53 | formatIdentifier: false, 54 | defined: options.defined, 55 | tmpPath: distPath, 56 | wat2wasm: wat2wasmPath, 57 | cheapPacketName: 'cheap/src', 58 | reportError(error) { 59 | console.error(error.message) 60 | } 61 | }) 62 | const transformed = ts.transform(program.getSourceFile(options.input), [transformerBefore], compilerOptions) 63 | 64 | if (options.output) { 65 | fs.writeFileSync(options.output, printer.printFile(transformed.transformed[0])) 66 | } 67 | 68 | return transformed.transformed[0] 69 | } 70 | 71 | export function check(source: string, target: string, options: { 72 | input: string 73 | output?: string 74 | defined?: Record 75 | include?: string[] 76 | }) { 77 | expect(compare(generateAST(target), transform2AST(source, options))).toBe(true) 78 | } 79 | -------------------------------------------------------------------------------- /src/transformer/function/parseImports.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript' 2 | import path from 'path' 3 | import getFilePath from './getFilePath' 4 | 5 | export default function parseImports(file: ts.SourceFile, program: ts.Program, typeChecker: ts.TypeChecker, locals: Map) { 6 | const map: Map 8 | specifier: string 9 | }> = new Map() 10 | 11 | file.statements.forEach((node) => { 12 | if (ts.isImportDeclaration(node)) { 13 | const ext = path.extname((node.moduleSpecifier as ts.StringLiteral).text) 14 | if (!ext || ext === '.ts') { 15 | const specifier = (node.moduleSpecifier as ts.StringLiteral).text.split('!').pop() 16 | const filePath = getFilePath(program, file.fileName, specifier) 17 | if (filePath) { 18 | const m = map.get(filePath) || { 19 | map: new Map(), 20 | specifier 21 | } 22 | if (node.importClause && ts.isImportClause(node.importClause)) { 23 | if (node.importClause.isTypeOnly 24 | || node.importClause.phaseModifier === ts.SyntaxKind.TypeKeyword 25 | ) { 26 | return 27 | } 28 | if (node.importClause.name && ts.isIdentifier(node.importClause.name)) { 29 | m.map.set('default', node.importClause.name.escapedText as string) 30 | locals.set(node.importClause.name.escapedText as string, typeChecker.getSymbolAtLocation(node.importClause.name)) 31 | } 32 | if (node.importClause.namedBindings) { 33 | if (ts.isNamedImports(node.importClause.namedBindings)) { 34 | node.importClause.namedBindings.elements.forEach((element) => { 35 | if (element.isTypeOnly) { 36 | return 37 | } 38 | if (element.propertyName && ts.isIdentifier(element.propertyName)) { 39 | m.map.set(element.propertyName.escapedText as string, element.name.escapedText as string) 40 | locals.set(element.propertyName.escapedText as string, typeChecker.getSymbolAtLocation(element.propertyName)) 41 | } 42 | else { 43 | m.map.set(element.name.escapedText as string, element.name.escapedText as string) 44 | locals.set(element.name.escapedText as string, typeChecker.getSymbolAtLocation(element.name)) 45 | } 46 | }) 47 | } 48 | else if (ts.isNamespaceImport(node.importClause.namedBindings)) { 49 | m.map.set('all', node.importClause.namedBindings.name.escapedText as string) 50 | } 51 | } 52 | } 53 | map.set(filePath, m) 54 | } 55 | } 56 | } 57 | }) 58 | 59 | return map 60 | } 61 | -------------------------------------------------------------------------------- /src/transformer/visitor/identifierVisitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import ts from 'typescript' 3 | import statement, { StageStatus } from '../statement' 4 | import { CTypeEnum2Type, Type2CTypeEnum } from '../defined' 5 | import { CTypeEnum } from '../../typedef' 6 | import * as nodeUtil from '../util/nodeutil' 7 | import { is } from '@libmedia/common' 8 | 9 | export default function (node: ts.Identifier, visitor: ts.Visitor): ts.Node | ts.Node[] { 10 | if ((statement.lookupStage(StageStatus.Parameter) || statement.lookupStage(StageStatus.VariableDeclaration)) 11 | && node.parent 12 | && ( 13 | ts.isVariableDeclaration(node.parent) && node.parent.name === node 14 | || ts.isBindingElement(node.parent) && node.parent.name === node 15 | || ts.isParameter(node.parent) && node.parent.name === node 16 | ) 17 | ) { 18 | statement.getCurrentStack().locals.set(node.escapedText as string, statement.typeChecker.getSymbolAtLocation(node)) 19 | } 20 | 21 | let parent = node.parent 22 | 23 | if (parent && ts.isAsExpression(parent)) { 24 | parent = node.parent.parent 25 | } 26 | 27 | if (node.escapedText === CTypeEnum2Type[CTypeEnum.null] 28 | && !statement.lookupLocal(node.escapedText) 29 | && parent && ((parent as ts.VariableDeclaration).initializer === node 30 | || ts.isBinaryExpression(parent) 31 | || ts.isCallExpression(parent) 32 | || ts.isReturnStatement(parent) 33 | || ts.isConditionalExpression(parent) 34 | || ts.isCaseClause(parent) 35 | || ts.isComputedPropertyName(parent) 36 | || ts.isArrayLiteralExpression(parent) 37 | || ts.isAsExpression(parent) 38 | || statement.getCurrentStage()?.stage === StageStatus.SingleArrowRight && !ts.isTypeReferenceNode(parent) 39 | ) 40 | ) { 41 | if (statement.cheapCompilerOptions.defined.WASM_64) { 42 | return nodeUtil.createBitInt(0) 43 | } 44 | else { 45 | return statement.context.factory.createNumericLiteral(0) 46 | } 47 | } 48 | else if (is.number(Type2CTypeEnum[node.escapedText as string]) 49 | && !statement.lookupLocal(node.escapedText as string) 50 | && parent && ( 51 | (parent as ts.VariableDeclaration).initializer === node 52 | || ts.isBinaryExpression(parent) 53 | || (ts.isCallExpression(parent) && parent.expression !== node) 54 | || ts.isReturnStatement(parent) 55 | || ts.isConditionalExpression(parent) 56 | || ts.isCaseClause(parent) 57 | || ts.isComputedPropertyName(parent) 58 | || ts.isElementAccessExpression(parent) && parent.argumentExpression === node 59 | || statement.getCurrentStage()?.stage === StageStatus.SingleArrowRight && !ts.isTypeReferenceNode(parent) 60 | ) 61 | ) { 62 | return statement.context.factory.createNumericLiteral(Type2CTypeEnum[node.escapedText as string]) 63 | } 64 | return ts.visitEachChild(node, visitor, statement.context) 65 | } 66 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/offsetof.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | import { symbolImport, definedMetaPropertyImport } from './snippet' 5 | 6 | describe('addressof', () => { 7 | 8 | let input: string 9 | let output: string 10 | 11 | beforeAll(() => { 12 | input = path.join(distPath, './offsetof_input.ts') 13 | output = path.join(distPath, './offsetof_output.ts') 14 | }) 15 | 16 | afterAll(() => { 17 | fs.unlinkSync(input) 18 | fs.rmSync(output, { force: true }) 19 | }) 20 | 21 | const snippetClassTestABSource = ` 22 | @struct 23 | class TestA { 24 | a: pointer 25 | b: uint8 26 | c: array 27 | } 28 | @struct 29 | class TestB { 30 | a: pointer 31 | b: TestA 32 | c: uint16 33 | } 34 | ` 35 | 36 | const snippetClassTestABTarget = ` 37 | class TestA { 38 | a: pointer; 39 | b: uint8; 40 | c: array; 41 | static { 42 | const prototype = this.prototype; 43 | const map = new Map(); 44 | map.set("a", { 0: 2, 1: 1, 2: 1, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 }); 45 | map.set("b", { 0: 2, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 4, 8: 0 }); 46 | map.set("c", { 0: 2, 1: 0, 2: 0, 3: 1, 4: 8, 5: 0, 6: 0, 7: 5, 8: 0 }); 47 | definedMetaProperty(prototype, symbolStruct, true); 48 | definedMetaProperty(prototype, symbolStructMaxBaseTypeByteLength, 4); 49 | definedMetaProperty(prototype, symbolStructLength, 16); 50 | definedMetaProperty(prototype, symbolStructKeysMeta, map); 51 | } 52 | } 53 | class TestB { 54 | a: pointer; 55 | b: TestA; 56 | c: uint16; 57 | static { 58 | const prototype = this.prototype; 59 | const map = new Map(); 60 | map.set("a", { 0: TestA, 1: 1, 2: 1, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 }); 61 | map.set("b", { 0: TestA, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 4, 8: 0 }); 62 | map.set("c", { 0: 6, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 20, 8: 0 }); 63 | definedMetaProperty(prototype, symbolStruct, true); 64 | definedMetaProperty(prototype, symbolStructMaxBaseTypeByteLength, 4); 65 | definedMetaProperty(prototype, symbolStructLength, 24); 66 | definedMetaProperty(prototype, symbolStructKeysMeta, map); 67 | } 68 | } 69 | ` 70 | 71 | test('offsetof(struct, a)', () => { 72 | const source = ` 73 | ${snippetClassTestABSource} 74 | let b = offsetof(TestA, 'a') 75 | let c = offsetof(TestB, 'c') 76 | ` 77 | const target = ` 78 | ${symbolImport} 79 | ${definedMetaPropertyImport} 80 | ${snippetClassTestABTarget} 81 | let b = 0 82 | let c = 20 83 | ` 84 | check(source, target, { 85 | input 86 | }) 87 | }) 88 | 89 | }) -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { 3 | memcpy, 4 | memcpyFromUint8Array, 5 | memmove, 6 | memset, 7 | mapSafeUint8Array, 8 | mapUint8Array, 9 | mapFloat32Array, 10 | mapFloat64Array, 11 | mapInt8Array, 12 | mapInt16Array, 13 | mapInt32Array, 14 | mapUint16Array, 15 | mapUint32Array, 16 | mapInt64Array, 17 | mapUint64Array, 18 | readCString, 19 | writeCString 20 | } from './std/memory' 21 | 22 | export { 23 | strcat, 24 | strcmp, 25 | strcpy, 26 | strlen 27 | } from './std/string' 28 | 29 | export { default as make } from './std/make' 30 | 31 | export { default as unmake } from './std/unmake' 32 | 33 | export { default as offsetof } from './std/offsetof' 34 | export { default as addressof } from './std/addressof' 35 | export { default as sizeof } from './std/sizeof' 36 | export { default as nullptrof } from './std/nullptrof' 37 | 38 | export { default as mapStruct } from './std/mapStruct' 39 | 40 | export { default as List } from './std/collection/List' 41 | 42 | export { default as getUniqueCounter32 } from './std/function/getUniqueCounter32' 43 | 44 | export { default as getUniqueCounter64 } from './std/function/getUniqueCounter64' 45 | 46 | export { default as getRandomValues } from './std/function/getRandomValues' 47 | 48 | export { default as isPointer } from './std/function/isPointer' 49 | 50 | export { default as isCStruct } from './std/function/isCStruct' 51 | 52 | export { default as WebAssemblyRunner, WebAssemblyRunnerOptions } from './webassembly/WebAssemblyRunner' 53 | 54 | export { default as compileResource, WebAssemblyResource, WebAssemblySource } from './webassembly/compiler' 55 | 56 | export { deTransferableSharedPtr } from './std/smartPtr/SharedPtr' 57 | 58 | export { default as SafeUint8Array } from './std/buffer/SafeUint8Array' 59 | 60 | export * as atomics from './thread/atomics' 61 | 62 | export { 63 | Thread, 64 | ThreadOptions, 65 | createThreadFromClass, 66 | createThreadFromFunction, 67 | createThreadFromModule, 68 | closeThread, 69 | joinThread 70 | } from './thread/thread' 71 | 72 | export { Mutex } from './thread/mutex' 73 | export { Sem } from './thread/semaphore' 74 | export { Cond } from './thread/cond' 75 | export { Barrier } from './thread/barrier' 76 | export { Sync } from './thread/sync' 77 | 78 | export * as cond from './thread/cond' 79 | export * as mutex from './thread/mutex' 80 | export * as semaphore from './thread/semaphore' 81 | export * as barrier from './thread/barrier' 82 | export * as stack from './stack' 83 | export * as sync from './thread/sync' 84 | 85 | export { 86 | CHeapError, 87 | POSIXError 88 | } from './error' 89 | 90 | export * as config from './config' 91 | 92 | export { 93 | Memory, 94 | Table, 95 | isMainThread, 96 | ThreadId 97 | } from './heap' 98 | 99 | export { default as ASMRunner } from './asm/ASMRunner' 100 | -------------------------------------------------------------------------------- /src/thread/cond.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 参考 https://github.com/mozilla-spidermonkey/js-lock-and-condition 3 | */ 4 | 5 | import * as mutexUtil from './mutex' 6 | import * as atomics from './atomics' 7 | import type { Mutex } from './mutex' 8 | 9 | @struct 10 | export class Cond { 11 | atomic: atomic_int32 12 | } 13 | 14 | /** 15 | * 初始化条件变量 16 | */ 17 | export function init(cond: pointer, attr?: pointer): int32 { 18 | atomics.store(addressof(cond.atomic), 0) 19 | return 0 20 | } 21 | 22 | /** 23 | * 销毁条件变量 24 | */ 25 | export function destroy(cond: pointer): int32 { 26 | atomics.store(addressof(cond.atomic), 0) 27 | return 0 28 | } 29 | 30 | /** 31 | * 唤醒条件变量上的一个等待线程 32 | * 33 | * @param cond 34 | */ 35 | export function signal(cond: pointer): int32 { 36 | atomics.add(addressof(cond.atomic), 1) 37 | atomics.notify(addressof(cond.atomic), 1) 38 | return 0 39 | } 40 | 41 | /** 42 | * 唤醒条件变量上的所有等待线程 43 | * 44 | * @param cond 45 | */ 46 | export function broadcast(cond: pointer): int32 { 47 | atomics.add(addressof(cond.atomic), 1) 48 | atomics.notify(addressof(cond.atomic), 1 << 30) 49 | return 0 50 | } 51 | 52 | /** 53 | * 线程在条件变量处等待 54 | * 55 | * @param cond 56 | * @param mutex 57 | * @returns 58 | */ 59 | export function wait(cond: pointer, mutex: pointer): int32 { 60 | let c = atomics.load(addressof(cond.atomic)) 61 | mutexUtil.unlock(mutex) 62 | atomics.wait(addressof(cond.atomic), c) 63 | mutexUtil.lock(mutex) 64 | return 0 65 | } 66 | 67 | /** 68 | * 线程在条件变量处异步等待 69 | * 70 | * @param cond 71 | * @param mutex 72 | */ 73 | export async function waitAsync(cond: pointer, mutex: pointer): Promise { 74 | let c = atomics.load(addressof(cond.atomic)) 75 | mutexUtil.unlock(mutex) 76 | 77 | await atomics.waitAsync(addressof(cond.atomic), c) 78 | 79 | await mutexUtil.lockAsync(mutex) 80 | 81 | return 0 82 | } 83 | 84 | /** 85 | * 线程在条件变量处超时等待 86 | * 87 | * @param cond 88 | * @param mutex 89 | * @param timeout 毫秒 90 | */ 91 | export function timedWait(cond: pointer, mutex: pointer, timeout: int32): int32 { 92 | let c = atomics.load(addressof(cond.atomic)) 93 | mutexUtil.unlock(mutex) 94 | let ret = atomics.waitTimeout(addressof(cond.atomic), c, timeout) 95 | mutexUtil.lock(mutex) 96 | return ret === 2 ? 110 : 0 97 | } 98 | 99 | /** 100 | * 线程在条件变量处超时异步等待 101 | * 102 | * @param cond 103 | * @param mutex 104 | * @param timeout 毫秒 105 | */ 106 | export async function timedwaitAsync(cond: pointer, mutex: pointer, timeout: int32): Promise { 107 | let c = atomics.load(addressof(cond.atomic)) 108 | mutexUtil.unlock(mutex) 109 | let ret = await atomics.waitTimeoutAsync(addressof(cond.atomic), c, timeout) 110 | await mutexUtil.lockAsync(mutex) 111 | return ret === 2 ? 110 : 0 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/webassembly/runtime/asm/thread.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 3 | import asm from './thread.asm' 4 | import asm64 from './thread64.asm' 5 | import type { Mutex } from '../../../thread/mutex' 6 | import type { Cond } from '../../../thread/cond' 7 | import type { Timespec } from '../semaphore' 8 | 9 | import { base64, wasm as wasmUtils, logger } from '@libmedia/common' 10 | 11 | /** 12 | * WebAssembly runtime 实例 13 | */ 14 | export let wasmThreadProxy: WebAssembly.Instance 15 | 16 | let support = true 17 | 18 | export function isSupport() { 19 | return support 20 | } 21 | 22 | export function init(memory: WebAssembly.Memory, initial: uint32, maximum: uint32, override: ( 23 | data: { 24 | wasm_pthread_mutex_lock: (mutex: pointer) => int32, 25 | wasm_pthread_mutex_trylock: (mutex: pointer) => int32, 26 | wasm_pthread_mutex_unlock: (mutex: pointer) => int32, 27 | wasm_pthread_cond_wait: (cond: pointer, mutex: pointer) => int32, 28 | wasm_pthread_cond_timedwait: (cond: pointer, mutex: pointer, abstime: pointer) => int32, 29 | wasm_pthread_cond_signal: (cond: pointer) => int32, 30 | wasm_pthread_cond_broadcast: (cond: pointer) => int32 31 | } 32 | ) => void) { 33 | try { 34 | if (typeof SharedArrayBuffer === 'function' && memory.buffer instanceof SharedArrayBuffer || defined(WASM_64)) { 35 | const wasm = wasmUtils.setMemoryMeta(base64.base64ToUint8Array(defined(WASM_64) ? asm64 : asm), { 36 | shared: defined(WASM_64) ? (typeof SharedArrayBuffer === 'function' && memory.buffer instanceof SharedArrayBuffer) : true, 37 | initial, 38 | maximum 39 | }) 40 | 41 | wasmThreadProxy = new WebAssembly.Instance(new WebAssembly.Module(wasm as BufferSource), { 42 | env: { 43 | memory 44 | } 45 | }) 46 | } 47 | else { 48 | support = false 49 | return 50 | } 51 | override({ 52 | wasm_pthread_mutex_lock: wasmThreadProxy.exports.lock as (mutex: pointer) => int32, 53 | wasm_pthread_mutex_trylock: wasmThreadProxy.exports.trylock as (mutex: pointer) => int32, 54 | wasm_pthread_mutex_unlock: wasmThreadProxy.exports.unlock as (mutex: pointer) => int32, 55 | wasm_pthread_cond_wait: wasmThreadProxy.exports.wait as (cond: pointer, mutex: pointer) => int32, 56 | wasm_pthread_cond_timedwait: wasmThreadProxy.exports.timedwait as ( 57 | cond: pointer, 58 | mutex: pointer, 59 | abstime: pointer 60 | ) => int32, 61 | wasm_pthread_cond_signal: wasmThreadProxy.exports.signal as (cond: pointer) => int32, 62 | wasm_pthread_cond_broadcast: wasmThreadProxy.exports.broadcast as (cond: pointer) => int32 63 | }) 64 | } 65 | catch (error) { 66 | support = false 67 | logger.warn('thread asm not support, cannot use asm thread function') 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/thread/initModule.ts: -------------------------------------------------------------------------------- 1 | import { SELF } from '@libmedia/common/constant' 2 | import { is } from '@libmedia/common' 3 | import { CHeapError } from '../error' 4 | 5 | import { 6 | IPCPort, 7 | REQUEST, 8 | NodeIPCPort 9 | } from '@libmedia/common/network' 10 | 11 | if (defined(ENV_NODE) && !defined(ENV_CJS)) { 12 | // @ts-ignore 13 | import { parentPort as parentPort_ } from 'worker_threads' 14 | } 15 | 16 | let parentPort = SELF 17 | if (defined(ENV_NODE)) { 18 | if (defined(ENV_CJS)) { 19 | const { parentPort: parentPort_ } = require('worker_threads') 20 | parentPort = parentPort_ 21 | } 22 | else { 23 | parentPort = parentPort_ 24 | } 25 | } 26 | 27 | export default function init(module: Object) { 28 | 29 | let ipc: IPCPort 30 | 31 | function initIPC(port: MessagePort) { 32 | ipc = defined(ENV_NODE) ? new NodeIPCPort(port) : new IPCPort(port) 33 | ipc.on(REQUEST, async (data: any) => { 34 | const method = data.method 35 | const params = data.params 36 | 37 | if (is.func(module[method])) { 38 | try { 39 | if (!module[method].transfer) { 40 | module[method].transfer = [] 41 | } 42 | const result = await module[method](...params.params) 43 | ipc.reply(data, result, null, module[method].transfer) 44 | module[method].transfer.length = 0 45 | } 46 | catch (error) { 47 | if (defined(DEBUG)) { 48 | console.error(error) 49 | } 50 | ipc.reply(data, CHeapError.REQUEST_ERROR, { 51 | message: error.message 52 | }) 53 | } 54 | } 55 | }) 56 | } 57 | 58 | const handler = (message: MessageEvent) => { 59 | const origin = defined(ENV_NODE) ? message : message.data 60 | const type = origin.type 61 | const data = origin.data 62 | 63 | switch (type) { 64 | case 'init': 65 | if (SELF.CHeap && SELF.CHeap.initThread && SELF.CHeap.Config.USE_THREADS) { 66 | SELF.CHeap.initThread(data).then(() => { 67 | parentPort.postMessage({ 68 | type: 'ready' 69 | }) 70 | }) 71 | return 72 | } 73 | parentPort.postMessage({ 74 | type: 'ready' 75 | }) 76 | break 77 | case 'run': 78 | 79 | parentPort.postMessage({ 80 | type: 'running' 81 | }) 82 | 83 | initIPC(data.port) 84 | 85 | break 86 | case 'stop': 87 | if (ipc) { 88 | ipc.destroy() 89 | } 90 | // @ts-ignore 91 | if (SELF.__freeSmartPtr__) { 92 | // @ts-ignore 93 | SELF.__freeSmartPtr__() 94 | } 95 | parentPort.postMessage({ 96 | type: 'stopped' 97 | }) 98 | break 99 | default: 100 | break 101 | } 102 | } 103 | 104 | if (defined(ENV_NODE)) { 105 | // @ts-ignore 106 | parentPort.on('message', handler) 107 | } 108 | else { 109 | parentPort.onmessage = handler 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/thread/initClass.ts: -------------------------------------------------------------------------------- 1 | import { SELF } from '@libmedia/common/constant' 2 | import { is } from '@libmedia/common' 3 | import { CHeapError } from '../error' 4 | 5 | if (defined(ENV_NODE) && !defined(ENV_CJS)) { 6 | // @ts-ignore 7 | import { parentPort as parentPort_ } from 'worker_threads' 8 | } 9 | 10 | import { 11 | IPCPort, 12 | REQUEST, 13 | NodeIPCPort 14 | } from '@libmedia/common/network' 15 | 16 | let parentPort = SELF 17 | if (defined(ENV_NODE)) { 18 | if (defined(ENV_CJS)) { 19 | const { parentPort: parentPort_ } = require('worker_threads') 20 | parentPort = parentPort_ 21 | } 22 | else { 23 | parentPort = parentPort_ 24 | } 25 | } 26 | 27 | export default function init(run: (...args: any[]) => any) { 28 | 29 | let ipc: IPCPort 30 | let target: any 31 | 32 | function initIPC(port: MessagePort) { 33 | ipc = defined(ENV_NODE) ? new NodeIPCPort(port) : new IPCPort(port) 34 | ipc.on(REQUEST, async (data: any) => { 35 | const method = data.method 36 | const params = data.params 37 | 38 | if (is.func(target[method])) { 39 | try { 40 | if (!target[method].transfer) { 41 | target[method].transfer = [] 42 | } 43 | const result = await target[method](...params.params) 44 | ipc.reply(data, result, null, target[method].transfer) 45 | target[method].transfer.length = 0 46 | } 47 | catch (error) { 48 | if (defined(DEBUG)) { 49 | console.error(error) 50 | } 51 | ipc.reply(data, CHeapError.REQUEST_ERROR, { 52 | message: error.message 53 | }) 54 | } 55 | } 56 | }) 57 | } 58 | 59 | const handler = (message: MessageEvent) => { 60 | const origin = defined(ENV_NODE) ? message : message.data 61 | const type = origin.type 62 | const data = origin.data 63 | 64 | switch (type) { 65 | case 'init': 66 | if (SELF.CHeap && SELF.CHeap.initThread && SELF.CHeap.Config.USE_THREADS) { 67 | SELF.CHeap.initThread(data).then(() => { 68 | parentPort.postMessage({ 69 | type: 'ready' 70 | }) 71 | }) 72 | return 73 | } 74 | parentPort.postMessage({ 75 | type: 'ready' 76 | }) 77 | break 78 | case 'run': 79 | 80 | parentPort.postMessage({ 81 | type: 'running' 82 | }) 83 | 84 | target = run(data.params) 85 | 86 | initIPC(data.port) 87 | 88 | break 89 | case 'stop': 90 | if (ipc) { 91 | ipc.destroy() 92 | } 93 | // @ts-ignore 94 | if (SELF.__freeSmartPtr__) { 95 | // @ts-ignore 96 | SELF.__freeSmartPtr__() 97 | } 98 | parentPort.postMessage({ 99 | type: 'stopped' 100 | }) 101 | break 102 | default: 103 | break 104 | } 105 | } 106 | 107 | if (defined(ENV_NODE)) { 108 | // @ts-ignore 109 | parentPort.on('message', handler) 110 | } 111 | else { 112 | parentPort.onmessage = handler 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/webassembly/runtime/asm/thread.asm.ts: -------------------------------------------------------------------------------- 1 | export default asm` 2 | (func $lock (export "lock") (param $mutexAddr i32) (result i32) 3 | (local $status i32) 4 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 0) (i32.const 1)) 5 | (local.set $status) 6 | (local.get $status) 7 | (i32.const 0) 8 | (i32.ne) 9 | if 10 | (loop $loop 11 | (local.get $status) 12 | (i32.const 2) 13 | (i32.eq) 14 | if (result i32) 15 | (i32.const 1) 16 | else 17 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 1) (i32.const 2)) 18 | (i32.const 0) 19 | (i32.ne) 20 | end 21 | if 22 | (memory.atomic.wait32 (local.get $mutexAddr) (i32.const 2) (i64.const -1)) 23 | (drop) 24 | end 25 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 0) (i32.const 2)) 26 | (local.set $status) 27 | (local.get $status) 28 | (i32.const 0) 29 | (i32.ne) 30 | (br_if $loop) 31 | ) 32 | end 33 | (i32.const 0) 34 | ) 35 | 36 | (func $trylock (export "trylock") (param $mutexAddr i32) (result i32) 37 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 0) (i32.const 1)) 38 | (i32.const 0) 39 | (i32.eq) 40 | if (result i32) 41 | (i32.const 0) 42 | else 43 | (i32.const 16) 44 | end 45 | ) 46 | 47 | (func $unlock (export "unlock") (param $mutexAddr i32) (result i32) 48 | (i32.atomic.rmw.sub (local.get $mutexAddr) (i32.const 1)) 49 | (i32.const 1) 50 | (i32.ne) 51 | if 52 | (i32.atomic.store (local.get $mutexAddr) (i32.const 0)) 53 | (memory.atomic.notify (local.get $mutexAddr) (i32.const 1)) 54 | (drop) 55 | end 56 | (i32.const 0) 57 | ) 58 | 59 | (func (export "wait") (param $condAddr i32) (param $mutexAddr i32) (result i32) 60 | (local.get $condAddr) 61 | (i32.atomic.load (local.get $condAddr)) 62 | (call $unlock (local.get $mutexAddr)) 63 | (drop) 64 | (i64.const -1) 65 | (memory.atomic.wait32) 66 | (drop) 67 | (call $lock (local.get $mutexAddr)) 68 | (drop) 69 | (i32.const 0) 70 | ) 71 | 72 | (func (export "timedwait") (param $condAddr i32) (param $mutexAddr i32) (param $timeout i32) (result i32) 73 | (local.get $condAddr) 74 | (i32.atomic.load (local.get $condAddr)) 75 | (call $unlock (local.get $mutexAddr)) 76 | (drop) 77 | (i64.add (i64.mul (i64.load (local.get $timeout)) (i64.const 1000000000)) (i64.extend_i32_u (i32.load offset=8 align=4 (local.get $timeout)))) 78 | (memory.atomic.wait32) 79 | (call $lock (local.get $mutexAddr)) 80 | (drop) 81 | (i32.const 2) 82 | (i32.eq) 83 | if (result i32) 84 | (i32.const 112) 85 | else 86 | (i32.const 0) 87 | end 88 | ) 89 | 90 | (func (export "signal") (param $condAddr i32) (result i32) 91 | (i32.atomic.rmw.add (local.get $condAddr) (i32.const 1)) 92 | (drop) 93 | (memory.atomic.notify (local.get $condAddr) (i32.const 1)) 94 | (drop) 95 | (i32.const 0) 96 | ) 97 | 98 | (func (export "broadcast") (param $condAddr i32) (result i32) 99 | (i32.atomic.rmw.add (local.get $condAddr) (i32.const 1)) 100 | (drop) 101 | (memory.atomic.notify (local.get $condAddr) (i32.const 1073741824)) 102 | (drop) 103 | (i32.const 0) 104 | ) 105 | ` 106 | -------------------------------------------------------------------------------- /src/webassembly/runtime/asm/thread64.asm.ts: -------------------------------------------------------------------------------- 1 | export default asm64` 2 | (func $lock (export "lock") (param $mutexAddr i64) (result i32) 3 | (local $status i32) 4 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 0) (i32.const 1)) 5 | (local.set $status) 6 | (local.get $status) 7 | (i32.const 0) 8 | (i32.ne) 9 | if 10 | (loop $loop 11 | (local.get $status) 12 | (i32.const 2) 13 | (i32.eq) 14 | if (result i32) 15 | (i32.const 1) 16 | else 17 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 1) (i32.const 2)) 18 | (i32.const 0) 19 | (i32.ne) 20 | end 21 | if 22 | (memory.atomic.wait32 (local.get $mutexAddr) (i32.const 2) (i64.const -1)) 23 | (drop) 24 | end 25 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 0) (i32.const 2)) 26 | (local.set $status) 27 | (local.get $status) 28 | (i32.const 0) 29 | (i32.ne) 30 | (br_if $loop) 31 | ) 32 | end 33 | (i32.const 0) 34 | ) 35 | 36 | (func $trylock (export "trylock") (param $mutexAddr i64) (result i32) 37 | (i32.atomic.rmw.cmpxchg (local.get $mutexAddr) (i32.const 0) (i32.const 1)) 38 | (i32.const 0) 39 | (i32.eq) 40 | if (result i32) 41 | (i32.const 0) 42 | else 43 | (i32.const 16) 44 | end 45 | ) 46 | 47 | (func $unlock (export "unlock") (param $mutexAddr i64) (result i32) 48 | (i32.atomic.rmw.sub (local.get $mutexAddr) (i32.const 1)) 49 | (i32.const 1) 50 | (i32.ne) 51 | if 52 | (i32.atomic.store (local.get $mutexAddr) (i32.const 0)) 53 | (memory.atomic.notify (local.get $mutexAddr) (i32.const 1)) 54 | (drop) 55 | end 56 | (i32.const 0) 57 | ) 58 | 59 | (func (export "wait") (param $condAddr i64) (param $mutexAddr i64) (result i32) 60 | (local.get $condAddr) 61 | (i32.atomic.load (local.get $condAddr)) 62 | (call $unlock (local.get $mutexAddr)) 63 | (drop) 64 | (i64.const -1) 65 | (memory.atomic.wait32) 66 | (drop) 67 | (call $lock (local.get $mutexAddr)) 68 | (drop) 69 | (i32.const 0) 70 | ) 71 | 72 | (func (export "timedwait") (param $condAddr i64) (param $mutexAddr i64) (param $timeout i64) (result i32) 73 | (local.get $condAddr) 74 | (i32.atomic.load (local.get $condAddr)) 75 | (call $unlock (local.get $mutexAddr)) 76 | (drop) 77 | (i64.add (i64.mul (i64.load (local.get $timeout)) (i64.const 1000000000)) (i64.extend_i32_u (i32.load offset=8 align=4 (local.get $timeout)))) 78 | (memory.atomic.wait32) 79 | (call $lock (local.get $mutexAddr)) 80 | (drop) 81 | (i32.const 2) 82 | (i32.eq) 83 | if (result i32) 84 | (i32.const 112) 85 | else 86 | (i32.const 0) 87 | end 88 | ) 89 | 90 | (func (export "signal") (param $condAddr i64) (result i32) 91 | (i32.atomic.rmw.add (local.get $condAddr) (i32.const 1)) 92 | (drop) 93 | (memory.atomic.notify (local.get $condAddr) (i32.const 1)) 94 | (drop) 95 | (i32.const 0) 96 | ) 97 | 98 | (func (export "broadcast") (param $condAddr i64) (result i32) 99 | (i32.atomic.rmw.add (local.get $condAddr) (i32.const 1)) 100 | (drop) 101 | (memory.atomic.notify (local.get $condAddr) (i32.const 1073741824)) 102 | (drop) 103 | (i32.const 0) 104 | ) 105 | ` 106 | -------------------------------------------------------------------------------- /src/polyfill/atomic.ts: -------------------------------------------------------------------------------- 1 | 2 | import { SELF } from '@libmedia/common/constant' 3 | 4 | function load( 5 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 6 | index: number 7 | ): number { 8 | return typedArray[index] 9 | } 10 | function store( 11 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 12 | index: number, 13 | value: number 14 | ): number { 15 | const old = typedArray[index] 16 | typedArray[index] = value 17 | return old 18 | } 19 | function add( 20 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 21 | index: number, 22 | value: number 23 | ): number { 24 | const old = typedArray[index] 25 | typedArray[index] = old + value 26 | return old 27 | } 28 | function sub( 29 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 30 | index: number, 31 | value: number 32 | ): number { 33 | const old = typedArray[index] 34 | typedArray[index] = old - value 35 | return old 36 | } 37 | function or( 38 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 39 | index: number, 40 | value: number 41 | ): number { 42 | const old = typedArray[index] 43 | typedArray[index] = old | value 44 | return old 45 | } 46 | function and( 47 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 48 | index: number, 49 | value: number 50 | ): number { 51 | const old = typedArray[index] 52 | typedArray[index] = old & value 53 | return old 54 | } 55 | function xor( 56 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 57 | index: number, 58 | value: number 59 | ): number { 60 | const old = typedArray[index] 61 | typedArray[index] = old ^ value 62 | return old 63 | } 64 | function compareExchange( 65 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 66 | index: number, 67 | expectedValue: number, 68 | replacementValue: number 69 | ): number { 70 | const old = typedArray[index] 71 | if (old === expectedValue) { 72 | typedArray[index] = replacementValue 73 | } 74 | return old 75 | } 76 | function exchange( 77 | typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, 78 | index: number, 79 | value: number 80 | ): number { 81 | const old = typedArray[index] 82 | typedArray[index] = value 83 | return old 84 | } 85 | function isLockFree(size: number): boolean { 86 | return true 87 | } 88 | 89 | function wait( 90 | typedArray: Int32Array, 91 | index: number, 92 | value: number, 93 | timeout?: number 94 | ): 'ok' | 'not-equal' | 'timed-out' { 95 | return 'ok' 96 | } 97 | function notify( 98 | typedArray: Int32Array, 99 | index: number, 100 | count?: number 101 | ): number { 102 | return 1 103 | } 104 | 105 | const Atomics = { 106 | add, 107 | compareExchange, 108 | exchange, 109 | isLockFree, 110 | load, 111 | and, 112 | or, 113 | store, 114 | sub, 115 | wait, 116 | notify, 117 | xor 118 | } 119 | 120 | export default function polyfill() { 121 | // @ts-ignore 122 | if (!SELF.Atomics) { 123 | // @ts-ignore 124 | SELF.Atomics = Atomics 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/thread/semaphore.ts: -------------------------------------------------------------------------------- 1 | import * as atomics from './atomics' 2 | import * as mutex from './mutex' 3 | import type { Mutex } from './mutex' 4 | 5 | @struct 6 | export class Sem { 7 | atomic: atomic_int32 8 | mutex: Mutex 9 | } 10 | 11 | 12 | /** 13 | * 初始化信号量 14 | * 15 | * @param sem 16 | * @param value 信号量初始值 17 | */ 18 | export function init(sem: pointer, value: uint32): int32 { 19 | atomics.store(addressof(sem.atomic), static_cast(value)) 20 | mutex.init(addressof(sem.mutex)) 21 | return 0 22 | } 23 | 24 | export function destroy(sem: pointer): int32 { 25 | mutex.destroy(addressof(sem.mutex)) 26 | return 0 27 | } 28 | 29 | /** 30 | * 生产信号量 31 | * 32 | * @param sem 33 | */ 34 | export function post(sem: pointer): int32 { 35 | atomics.add(addressof(sem.atomic), 1) 36 | atomics.notify(addressof(sem.atomic), 1) 37 | return 0 38 | } 39 | 40 | /** 41 | * 消费信号量 42 | * 43 | * @param sem 44 | */ 45 | export function wait(sem: pointer): int32 { 46 | while (true) { 47 | atomics.wait(addressof(sem.atomic), 0) 48 | let old = atomics.sub(addressof(sem.atomic), 1) 49 | if (old <= 0) { 50 | // 此时已经没有了,将减掉的加回来继续等待 51 | atomics.add(addressof(sem.atomic), 1) 52 | } 53 | else { 54 | break 55 | } 56 | } 57 | return 0 58 | } 59 | 60 | /** 61 | * 消费信号量 62 | * 63 | * @param sem 64 | */ 65 | export function tryWait(sem: pointer): int32 { 66 | mutex.lock(addressof(sem.mutex)) 67 | if (sem.atomic > 0) { 68 | sem.atomic-- 69 | mutex.unlock(addressof(sem.mutex)) 70 | return 0 71 | } 72 | mutex.unlock(addressof(sem.mutex)) 73 | return 11 74 | } 75 | 76 | /** 77 | * 消费信号量,并设置一个超时 78 | * 79 | * @param sem 80 | * @param timeout 毫秒 81 | * @returns 82 | */ 83 | export function timedWait(sem: pointer, timeout: int32): int32 { 84 | let ret = atomics.waitTimeout(addressof(sem.atomic), 0, timeout) 85 | if (ret !== 2) { 86 | let old = atomics.sub(addressof(sem.atomic), 1) 87 | if (old <= 0) { 88 | // 此时已经没有了,将减掉的加回来 89 | atomics.add(addressof(sem.atomic), 1) 90 | // ETIMEDOUT 91 | return 110 92 | } 93 | } 94 | return 0 95 | } 96 | 97 | /** 98 | * 异步消费信号量 99 | * 100 | * @param sem 101 | */ 102 | export async function waitAsync(sem: pointer): Promise { 103 | while (true) { 104 | await atomics.waitAsync(addressof(sem.atomic), 0) 105 | let old = atomics.sub(addressof(sem.atomic), 1) 106 | if (old <= 0) { 107 | // 此时已经没有了,将减掉的加回来继续等待 108 | atomics.add(addressof(sem.atomic), 1) 109 | } 110 | else { 111 | break 112 | } 113 | } 114 | return 0 115 | } 116 | 117 | /** 118 | * 异步消费信号量,并设置一个超时 119 | * 120 | * @param sem 121 | * @param timeout 毫秒 122 | */ 123 | export async function timedWaitAsync(sem: pointer, timeout: int32): Promise { 124 | let ret = await atomics.waitTimeoutAsync(addressof(sem.atomic), 0, timeout) 125 | if (ret !== 2) { 126 | let old = atomics.sub(addressof(sem.atomic), 1) 127 | if (old <= 0) { 128 | // 此时已经没有了,将减掉的加回来 129 | atomics.add(addressof(sem.atomic), 1) 130 | // ETIMEDOUT 131 | return 110 132 | } 133 | } 134 | return 0 135 | } 136 | 137 | 138 | export function set(sem: pointer, value: int32): int32 { 139 | atomics.store(addressof(sem.atomic), value) 140 | return 0 141 | } 142 | 143 | export function get(sem: pointer): int32 { 144 | return atomics.load(addressof(sem.atomic)) 145 | } 146 | 147 | -------------------------------------------------------------------------------- /src/thread/mutex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 参考 https://github.com/mozilla-spidermonkey/js-lock-and-condition 3 | */ 4 | 5 | import { POSIXError } from '../error' 6 | import * as atomics from './atomics' 7 | import { isWorker } from '@libmedia/common' 8 | 9 | @struct 10 | export class Mutex { 11 | atomic: atomic_int32 12 | } 13 | 14 | const enum STATUS { 15 | UNLOCK, 16 | LOCKED, 17 | WAITED 18 | } 19 | 20 | /** 21 | * 初始化锁 22 | * 23 | * @param mutex 24 | * @returns 25 | */ 26 | export function init(mutex: pointer): int32 { 27 | atomics.store(addressof(mutex.atomic), STATUS.UNLOCK) 28 | return 0 29 | } 30 | 31 | /** 32 | * 加锁 33 | * 34 | * @param mutex 35 | * @param spin 是否自旋 36 | */ 37 | export function lock(mutex: pointer, spin: boolean = false): int32 { 38 | let status: STATUS 39 | // 不为 UNLOCK,说明其他线程持锁,将锁置为 LOCKED 状态 40 | if ((status = atomics.compareExchange(addressof(mutex.atomic), STATUS.UNLOCK, STATUS.LOCKED)) !== STATUS.UNLOCK) { 41 | do { 42 | // 如果依旧得不到锁,将锁置为 WAITED 状态 43 | if (status == STATUS.WAITED 44 | || atomics.compareExchange(addressof(mutex.atomic), STATUS.LOCKED, STATUS.WAITED) !== STATUS.UNLOCK 45 | ) { 46 | // 主线程不能 wait,直接自旋(需要注意所有线程各自的持锁时间,避免出现忙等占用大量 cpu 时间) 47 | if (!spin && isWorker()) { 48 | atomics.wait(addressof(mutex.atomic), STATUS.WAITED) 49 | } 50 | } 51 | } 52 | // 再次尝试获取锁 53 | while ((status = atomics.compareExchange(addressof(mutex.atomic), STATUS.UNLOCK, STATUS.WAITED)) !== STATUS.UNLOCK) 54 | } 55 | 56 | return 0 57 | } 58 | 59 | /** 60 | * 尝试加锁 61 | * 62 | * @param mutex 63 | */ 64 | export function tryLock(mutex: pointer): int32 { 65 | if (atomics.compareExchange(addressof(mutex.atomic), STATUS.UNLOCK, STATUS.LOCKED) === STATUS.UNLOCK) { 66 | return 0 67 | } 68 | // EBUSY 69 | return POSIXError.EBUSY 70 | } 71 | 72 | 73 | /** 74 | * 异步加锁 75 | * 76 | * @param mutex 77 | */ 78 | export async function lockAsync(mutex: pointer): Promise { 79 | let status: STATUS 80 | // 不为 UNLOCK,说明其他线程持锁,将锁置为 LOCKED 状态 81 | if ((status = atomics.compareExchange(addressof(mutex.atomic), STATUS.UNLOCK, STATUS.LOCKED)) !== STATUS.UNLOCK) { 82 | do { 83 | // 如果依旧得不到锁,将锁置为 WAITED 状态 84 | if (status == STATUS.WAITED 85 | || atomics.compareExchange(addressof(mutex.atomic), STATUS.LOCKED, STATUS.WAITED) !== STATUS.UNLOCK 86 | ) { 87 | await atomics.waitAsync(addressof(mutex.atomic), STATUS.WAITED) 88 | } 89 | } 90 | // 再次尝试获取锁 91 | while ((status = atomics.compareExchange(addressof(mutex.atomic), STATUS.UNLOCK, STATUS.WAITED)) !== STATUS.UNLOCK) 92 | } 93 | 94 | return 0 95 | } 96 | 97 | /** 98 | * 释放锁 99 | * 100 | * @param mutex 101 | */ 102 | export function unlock(mutex: pointer): int32 { 103 | 104 | assert(atomics.load(addressof(mutex.atomic)) === STATUS.LOCKED || atomics.load(addressof(mutex.atomic)) === STATUS.WAITED) 105 | 106 | let status: STATUS = atomics.sub(addressof(mutex.atomic), 1) 107 | // 此时拥有锁,状态为 LOCKED 或 WAITED 108 | if (status !== STATUS.LOCKED) { 109 | // 释放锁 110 | atomics.store(addressof(mutex.atomic), STATUS.UNLOCK) 111 | // 唤醒一个 wait 的线程 112 | atomics.notify(addressof(mutex.atomic), 1) 113 | } 114 | 115 | return 0 116 | } 117 | 118 | /** 119 | * 销毁锁 120 | * 121 | * @param mutex 122 | * @returns 123 | */ 124 | export function destroy(mutex: pointer): int32 { 125 | atomics.store(addressof(mutex.atomic), STATUS.UNLOCK) 126 | return 0 127 | } 128 | -------------------------------------------------------------------------------- /src/transformer/defined.ts: -------------------------------------------------------------------------------- 1 | import { CTypeEnum } from '..//typedef' 2 | import * as constant from './constant' 3 | import { object, array } from '@libmedia/common' 4 | 5 | export const BuiltinType = [ 6 | 'i32', 7 | 'i64', 8 | 'f32', 9 | 'f64', 10 | 'uint8', 11 | 'uint16', 12 | 'uint32', 13 | 'uint64', 14 | 'int8', 15 | 'int16', 16 | 'int32', 17 | 'int64', 18 | 'float', 19 | 'float64', 20 | 'double', 21 | 'char', 22 | 'size', 23 | 'void', 24 | 'bool', 25 | 'size', 26 | 27 | 'atomic_char', 28 | 'atomic_uint8', 29 | 'atomic_uint16', 30 | 'atomic_uint32', 31 | 'atomic_int8', 32 | 'atomic_int16', 33 | 'atomic_int32', 34 | 'atomic_int64', 35 | 'atomic_uint64', 36 | 'atomic_bool' 37 | ] 38 | 39 | export const BuiltinAtomicType = [ 40 | 'atomic_char', 41 | 'atomic_uint8', 42 | 'atomic_uint16', 43 | 'atomic_uint32', 44 | 'atomic_int8', 45 | 'atomic_int16', 46 | 'atomic_int32', 47 | 'atomic_int64', 48 | 'atomic_uint64', 49 | 'atomic_bool' 50 | ] 51 | 52 | export const BuiltinDecorator = [ 53 | constant.cstruct, 54 | constant.cunion, 55 | constant.cignore, 56 | constant.ctype, 57 | constant.cpointer, 58 | constant.carray, 59 | constant.cbitField, 60 | constant.cinline, 61 | constant.cdeasync 62 | ] 63 | 64 | export const AtomicCall = [ 65 | 'add', 66 | 'sub', 67 | 'and', 68 | 'or', 69 | 'xor', 70 | 'store', 71 | 'load', 72 | 'compareExchange', 73 | 'exchange' 74 | ] 75 | 76 | export const BuiltinFloat = [ 77 | 'float', 78 | 'float64', 79 | 'double', 80 | 'f32', 81 | 'f64' 82 | ] 83 | 84 | export const BuiltinBigInt = [ 85 | 'i64', 86 | 'int64', 87 | 'uint64', 88 | 'atomic_int64', 89 | 'atomic_uint64' 90 | ] 91 | 92 | export const BuiltinUint = [ 93 | 'uint8', 94 | 'atomic_uint8', 95 | 'uint16', 96 | 'atomic_uint16', 97 | 'uint32', 98 | 'atomic_uint32', 99 | 'uint64', 100 | 'atomic_uint64', 101 | 'size' 102 | ] 103 | 104 | export const BuiltinBool = [ 105 | 'bool', 106 | 'atomic_bool' 107 | ] 108 | 109 | export const CTypeEnum2Type: Record = { 110 | [CTypeEnum.uint8]: 'uint8', 111 | [CTypeEnum.atomic_uint8]: 'atomic_uint8', 112 | [CTypeEnum.char]: 'char', 113 | [CTypeEnum.atomic_char]: 'atomic_char', 114 | [CTypeEnum.uint16]: 'uint16', 115 | [CTypeEnum.atomic_uint16]: 'atomic_uint16', 116 | [CTypeEnum.uint32]: 'uint32', 117 | [CTypeEnum.atomic_uint32]: 'atomic_uint32', 118 | [CTypeEnum.uint64]: 'uint64', 119 | [CTypeEnum.int8]: 'int8', 120 | [CTypeEnum.atomic_int8]: 'atomic_int8', 121 | [CTypeEnum.int16]: 'int16', 122 | [CTypeEnum.atomic_int16]: 'atomic_int16', 123 | [CTypeEnum.int32]: 'int32', 124 | [CTypeEnum.atomic_int32]: 'atomic_int32', 125 | [CTypeEnum.int64]: 'int64', 126 | [CTypeEnum.float]: 'float', 127 | [CTypeEnum.double]: 'double', 128 | [CTypeEnum.pointer]: 'pointer', 129 | [CTypeEnum.void]: 'void', 130 | [CTypeEnum.null]: 'nullptr', 131 | [CTypeEnum.atomic_uint64]: 'atomic_uint64', 132 | [CTypeEnum.atomic_int64]: 'atomic_int64', 133 | [CTypeEnum.bool]: 'bool', 134 | [CTypeEnum.atomic_bool]: 'atomic_bool', 135 | [CTypeEnum.size]: 'size' 136 | } 137 | 138 | export const Type2CTypeEnum: Record = { 139 | typeptr: CTypeEnum.pointer, 140 | i32: CTypeEnum.int32, 141 | i64: CTypeEnum.int64, 142 | f32: CTypeEnum.float, 143 | f64: CTypeEnum.double 144 | } 145 | 146 | object.each(CTypeEnum2Type, (value, key) => { 147 | Type2CTypeEnum[value] = +key 148 | }) 149 | 150 | export const BuiltinNumber = array.exclude(array.exclude(array.exclude(BuiltinType, BuiltinFloat), BuiltinBigInt), BuiltinBool) 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@libmedia/cheap", 3 | "version": "1.2.0", 4 | "description": "一个多线程和高性能 wasm 开发库", 5 | "sideEffects": [ 6 | "./src/heap.ts", 7 | "./src/thread/initClass.ts", 8 | "./src/thread/initFunction.ts", 9 | "./src/thread/initModule.ts", 10 | "./src/webassembly/runThread.ts", 11 | "./dist/esm/heap.js", 12 | "./dist/esm/thread/initClass.js", 13 | "./dist/esm/thread/initFunction.js", 14 | "./dist/esm/thread/initModule.js", 15 | "./dist/esm/webassembly/runThread.js", 16 | "./dist/mjs/heap.js", 17 | "./dist/mjs/thread/initClass.js", 18 | "./dist/mjs/thread/initFunction.js", 19 | "./dist/mjs/thread/initModule.js", 20 | "./dist/mjs/webassembly/runThread.js", 21 | "./dist/cjs/heap.js", 22 | "./dist/cjs/thread/initClass.js", 23 | "./dist/cjs/thread/initFunction.js", 24 | "./dist/cjs/thread/initModule.js", 25 | "./dist/cjs/webassembly/runThread.js", 26 | "./dist/cheap-polyfill.js" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/zhaohappy/cheap.git" 31 | }, 32 | "scripts": { 33 | "build-transformer": "rollup -c --environment transformer:1", 34 | "build-wasm-opt": "rollup -c --environment wasm_opt:1", 35 | "test-transformer": "jest --verbose", 36 | "prepublishOnly": "node ./scripts/check-publish-status.js" 37 | }, 38 | "files": [ 39 | "dist", 40 | "include", 41 | "build", 42 | "src" 43 | ], 44 | "author": "Gaoxing Zhao", 45 | "license": "MIT", 46 | "devDependencies": { 47 | "@jest/globals": "^30.2.0", 48 | "@rollup/plugin-node-resolve": "^16.0.3", 49 | "@rollup/plugin-typescript": "^12.3.0", 50 | "@types/jest": "^29.5.14", 51 | "@types/node": "^22.10.1", 52 | "cross-env": "^7.0.3", 53 | "jest": "^30.2.0", 54 | "rollup": "^4.53.2", 55 | "ts-jest": "^29.4.5", 56 | "ts-jest-mock-import-meta": "^1.3.1", 57 | "typescript": "^5.9.3", 58 | "yargs": "^13.3.0" 59 | }, 60 | "dependencies": { 61 | "@libmedia/common": "~2.2.0", 62 | "commander": "^13.1.0" 63 | }, 64 | "type": "module", 65 | "types": "./dist/esm/index.d.ts", 66 | "main": "./dist/cjs/index.cjs", 67 | "module": "./dist/mjs/index.js", 68 | "browser": "./dist/esm/index.js", 69 | "exports": { 70 | ".": { 71 | "browser": "./dist/esm/index.js", 72 | "import": "./dist/mjs/index.js", 73 | "require": "./dist/cjs/index.cjs", 74 | "default": "./dist/mjs/index.js", 75 | "types": "./dist/esm/index.d.ts" 76 | }, 77 | "./internal": { 78 | "browser": "./dist/esm/internal.js", 79 | "import": "./dist/mjs/internal.js", 80 | "require": "./dist/cjs/internal.cjs", 81 | "default": "./dist/mjs/internal.js", 82 | "types": "./dist/esm/internal.d.ts" 83 | }, 84 | "./cheapdef": { 85 | "browser": "./dist/esm/cheapdef.js", 86 | "import": "./dist/mjs/cheapdef.js", 87 | "require": "./dist/cjs/cheapdef.cjs", 88 | "default": "./dist/mjs/cheapdef.js", 89 | "types": "./dist/esm/cheapdef.d.ts" 90 | }, 91 | "./runThread": { 92 | "browser": "./dist/esm/thread/runThread.js", 93 | "import": "./dist/mjs/thread/runThread.js", 94 | "require": "./dist/cjs/thread/runThread.cjs", 95 | "default": "./dist/mjs/thread/runThread.js", 96 | "types": "./dist/esm/thread/runThread.d.ts" 97 | }, 98 | "./build/CheapPlugin": "./build/webpack/plugin/CheapPlugin.js", 99 | "./build/transformer": { 100 | "import": "./build/transformer.mjs", 101 | "require": "./build/transformer.cjs", 102 | "default": "./build/transformer.mjs", 103 | "types": "./build/transformer.d.ts" 104 | }, 105 | "./build/wasm-opt": { 106 | "require": "./build/wasm-opt.cjs", 107 | "import": "./build/wasm-opt.mjs", 108 | "default": "./build/wasm-opt.mjs" 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /include/wasmpthread.h: -------------------------------------------------------------------------------- 1 | #ifndef __WASM_PTHREAD_H__ 2 | #define __WASM_PTHREAD_H__ 3 | 4 | #include "./wasmenv.h" 5 | #include 6 | 7 | typedef char wasm_pthread_mutex_attr; 8 | 9 | struct pthread_mutex { 10 | int atomic; 11 | }; 12 | 13 | 14 | typedef struct pthread_mutex wasm_pthread_mutex_t; 15 | 16 | #define WASM_PTHREAD_MUTEX_INITIALIZER {0} 17 | 18 | EM_PORT_API(int) wasm_pthread_mutex_init(wasm_pthread_mutex_t* __restrict mutex, wasm_pthread_mutex_attr* __restrict attr); 19 | 20 | EM_PORT_API(int) wasm_pthread_mutex_destroy(wasm_pthread_mutex_t* mutex); 21 | 22 | EM_PORT_API(int) wasm_pthread_mutex_lock(wasm_pthread_mutex_t* mutex); 23 | EM_PORT_API(int) wasm_pthread_mutex_trylock(wasm_pthread_mutex_t* mutex); 24 | 25 | EM_PORT_API(int) wasm_pthread_mutex_unlock(wasm_pthread_mutex_t* mutex); 26 | 27 | EM_PORT_API(int) wasm_pthread_num_processors_np(); 28 | 29 | 30 | typedef char wasm_pthread_condattr_t; 31 | 32 | struct pthread_cond { 33 | int atomic; 34 | }; 35 | 36 | typedef struct pthread_cond wasm_pthread_cond_t; 37 | 38 | // 创建和销毁条件变量 39 | EM_PORT_API(int) wasm_pthread_cond_init(wasm_pthread_cond_t* cond, const wasm_pthread_condattr_t* attr); 40 | EM_PORT_API(int) wasm_pthread_cond_destroy(wasm_pthread_cond_t *cond); 41 | 42 | // 等待条件变量满足 43 | EM_PORT_API(int) wasm_pthread_cond_wait(wasm_pthread_cond_t* cond, wasm_pthread_mutex_t* mutex); 44 | EM_PORT_API(int) wasm_pthread_cond_timedwait(wasm_pthread_cond_t* cond, wasm_pthread_mutex_t* mutex, const struct timespec* abstime); 45 | 46 | // 信号通知等待条件的线程 47 | EM_PORT_API(int) wasm_pthread_cond_signal(wasm_pthread_cond_t *cond); 48 | EM_PORT_API(int) wasm_pthread_cond_broadcast(wasm_pthread_cond_t *cond); 49 | 50 | typedef char wasm_pthread_attr_t; 51 | 52 | struct pthread { 53 | int id; 54 | void* retval; 55 | int flags; 56 | int status; 57 | 58 | #if defined(__cplusplus) 59 | 60 | pthread(): id(0), retval(nullptr), flags(0), status(0) { 61 | 62 | } 63 | 64 | pthread(int v): id(0), retval(nullptr), flags(0), status(0) { 65 | 66 | } 67 | 68 | explicit operator bool() const { 69 | return id != 0; 70 | } 71 | 72 | pthread& operator=(const pthread& other) { 73 | id = other.id; 74 | retval = other.retval; 75 | flags = other.flags; 76 | status = other.status; 77 | return *this; 78 | } 79 | 80 | pthread& operator=(const int c) { 81 | if (c == 0) { 82 | id = 0; 83 | retval = nullptr; 84 | flags = 0; 85 | status = 0; 86 | } 87 | return *this; 88 | } 89 | #endif 90 | }; 91 | 92 | typedef struct pthread wasm_pthread_t; 93 | 94 | struct pthread_once { 95 | int atomic; 96 | }; 97 | 98 | typedef struct pthread_once wasm_pthread_once_t; 99 | 100 | #define WASM_PTHREAD_ONCE_INIT {0} 101 | #define WASM_PTHREAD_T_INIT {0, 0, 0, 0} 102 | 103 | EM_PORT_API(int) wasm_pthread_create(wasm_pthread_t* thread, const wasm_pthread_attr_t* attr, void *(*start_routine)(void *), void* arg); 104 | EM_PORT_API(int) wasm_pthread_join2(wasm_pthread_t* thread, void** retval); 105 | EM_PORT_API(void) wasm_pthread_exit(void* retval); 106 | EM_PORT_API(int) wasm_pthread_detach2(wasm_pthread_t* thread); 107 | EM_PORT_API(int) wasm_pthread_equal2(wasm_pthread_t* t1, wasm_pthread_t* t2); 108 | EM_PORT_API(int) wasm_pthread_once(wasm_pthread_once_t *once_control, void (*init_routine)(void)); 109 | EM_PORT_API(wasm_pthread_t*) wasm_pthread_self2(); 110 | 111 | #define wasm_pthread_join(thread, retval) \ 112 | wasm_pthread_join2(&thread, retval) 113 | 114 | #define wasm_pthread_detach(thread) \ 115 | wasm_pthread_detach2(&thread) 116 | 117 | #define wasm_pthread_equal(t1, t2) \ 118 | wasm_pthread_equal2(&t1, &t2) 119 | 120 | #define wasm_pthread_self() \ 121 | *wasm_pthread_self2() 122 | 123 | #endif -------------------------------------------------------------------------------- /src/transformer/__test__/case/deasync.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('unary', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './unary_input.ts') 12 | output = path.join(distPath, './unary_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('function', () => { 21 | const source = ` 22 | // @ts-ignore 23 | @deasync 24 | async function a() { 25 | 26 | } 27 | 28 | async function b() { 29 | await a() 30 | } 31 | ` 32 | const target = ` 33 | // @ts-ignore 34 | function a() { 35 | } 36 | async function b() { 37 | a() 38 | } 39 | ` 40 | check(source, target, { 41 | input, 42 | defined: { 43 | ENABLE_SYNCHRONIZE_API: true 44 | } 45 | }) 46 | }) 47 | 48 | test('function block', () => { 49 | const source = ` 50 | // @ts-ignore 51 | async function a() { 52 | 53 | } 54 | @deasync 55 | async function b() { 56 | while (true) { 57 | await a() 58 | break 59 | } 60 | } 61 | ` 62 | const target = ` 63 | // @ts-ignore 64 | async function a() { 65 | } 66 | function b() { 67 | while (true) { 68 | a() 69 | break 70 | } 71 | } 72 | ` 73 | check(source, target, { 74 | input, 75 | defined: { 76 | ENABLE_SYNCHRONIZE_API: true 77 | } 78 | }) 79 | }) 80 | 81 | test('class', () => { 82 | const source = ` 83 | class A { 84 | @deasync 85 | async a() { 86 | 87 | } 88 | } 89 | async function b() { 90 | const a = new A() 91 | await a.a() 92 | } 93 | ` 94 | const target = ` 95 | class A { 96 | a() { 97 | 98 | } 99 | } 100 | async function b() { 101 | const a = new A() 102 | a.a() 103 | } 104 | ` 105 | check(source, target, { 106 | input, 107 | defined: { 108 | ENABLE_SYNCHRONIZE_API: true 109 | } 110 | }) 111 | }) 112 | 113 | test('xx.class', () => { 114 | const source = ` 115 | class A { 116 | @deasync 117 | async a() { 118 | 119 | } 120 | } 121 | const context = { 122 | a: new A() 123 | } 124 | 125 | async function b() { 126 | await context.a.a() 127 | } 128 | ` 129 | const target = ` 130 | class A { 131 | a() { 132 | 133 | } 134 | } 135 | const context = { 136 | a: new A() 137 | } 138 | async function b() { 139 | context.a.a() 140 | } 141 | ` 142 | check(source, target, { 143 | input, 144 | defined: { 145 | ENABLE_SYNCHRONIZE_API: true 146 | } 147 | }) 148 | }) 149 | 150 | test('abstract class', () => { 151 | const source = ` 152 | abstract class A { 153 | // @ts-ignore 154 | @deasync 155 | abstract a(): Promise 156 | } 157 | class B extends A { 158 | async a() { 159 | 160 | } 161 | } 162 | async function b() { 163 | const a = new B() 164 | await a.a() 165 | } 166 | ` 167 | const target = ` 168 | abstract class A { 169 | abstract a(): Promise 170 | } 171 | class B extends A { 172 | a() { 173 | 174 | } 175 | } 176 | async function b() { 177 | const a = new B() 178 | a.a() 179 | } 180 | ` 181 | check(source, target, { 182 | input, 183 | defined: { 184 | ENABLE_SYNCHRONIZE_API: true 185 | } 186 | }) 187 | }) 188 | }) -------------------------------------------------------------------------------- /src/transformer/visitor/function/processAsm.ts: -------------------------------------------------------------------------------- 1 | 2 | import { execSync } from 'child_process' 3 | import statement from '../../statement' 4 | import ts from 'typescript' 5 | import reportError from '../../function/reportError' 6 | import { array } from '@libmedia/common' 7 | import * as errorType from '../../error' 8 | import * as fs from 'fs' 9 | 10 | const input = '__cheap__transformer_tmp.wat' 11 | const output = '__cheap__transformer_tmp.wasm' 12 | 13 | 14 | export default function processAsm(template: ts.TemplateExpression | ts.NoSubstitutionTemplateLiteral, node: ts.TaggedTemplateExpression, wasm64: boolean) { 15 | 16 | let text = '' 17 | let startPos = node.template.getStart() 18 | 19 | if (ts.isNoSubstitutionTemplateLiteral(template)) { 20 | text = template.text 21 | } 22 | else { 23 | if (template.head) { 24 | text += template.head.text 25 | } 26 | 27 | for (let i = 0; i < template.templateSpans.length; i++) { 28 | const span = template.templateSpans[i] 29 | if (ts.isStringLiteral(span.expression) 30 | || ts.isNumericLiteral(span.expression) 31 | ) { 32 | text += span.expression.text 33 | text += span.literal.text 34 | } 35 | else { 36 | reportError(statement.currentFile, span.expression, `expression ${span.expression.getText()} not support in asm`) 37 | return statement.context.factory.createStringLiteral('compile asm error') 38 | } 39 | } 40 | } 41 | 42 | const distPath = `${statement.options.tmpPath ? `${statement.options.tmpPath}/` : ''}` 43 | 44 | if (distPath && !ts.sys.directoryExists(distPath)) { 45 | ts.sys.createDirectory(distPath) 46 | } 47 | 48 | const inputPath = `${distPath}${input}` 49 | const outputPath = `${distPath}${output}` 50 | 51 | const cmd = `${statement.options.wat2wasm} ${inputPath} --enable-all -o ${outputPath}` 52 | 53 | const source = ` 54 | (module 55 | (import "env" "memory" (memory${wasm64 ? ' i64 ' : ' '}1 65536 shared)) 56 | ${text} 57 | ) 58 | ` 59 | 60 | ts.sys.writeFile(inputPath, source) 61 | 62 | try { 63 | execSync(cmd, { 64 | stdio: 'pipe' 65 | }) 66 | const buffer = fs.readFileSync(outputPath) 67 | return statement.context.factory.createStringLiteral(buffer.toString('base64')) 68 | } 69 | catch (error) { 70 | let messages: string[] = error.message.split('\n') 71 | messages.shift() 72 | 73 | let errorMessage = '' 74 | let line = 0 75 | 76 | function getPos(line: number) { 77 | let pos = 0 78 | 79 | while (line && pos < text.length) { 80 | if (text[pos++] === '\n') { 81 | line-- 82 | } 83 | } 84 | 85 | while (pos < text.length) { 86 | if (!/\s/.test(text[pos])) { 87 | break 88 | } 89 | pos++ 90 | } 91 | 92 | const start = startPos + pos 93 | 94 | while (pos < text.length) { 95 | if (text[pos] === '\n') { 96 | break 97 | } 98 | pos++ 99 | } 100 | const end = startPos + pos 101 | 102 | return { 103 | start, end 104 | } 105 | } 106 | 107 | array.each(messages, (message) => { 108 | const match = message.match(/__cheap__transformer_tmp.wat:(\d+)/) 109 | if (match) { 110 | if (errorMessage) { 111 | const { start, end } = getPos(line) 112 | reportError(statement.currentFile, node, errorMessage, errorType.SYNTAX_ERROR, start, end) 113 | } 114 | errorMessage = `${message.split('error: ').pop()}` 115 | line = +match[1] - 4 116 | } 117 | else if (message) { 118 | errorMessage += `\n${message}` 119 | } 120 | }) 121 | 122 | if (errorMessage) { 123 | const { start, end } = getPos(line) 124 | reportError(statement.currentFile, node, errorMessage, errorType.SYNTAX_ERROR, start, end) 125 | } 126 | 127 | return statement.context.factory.createStringLiteral('compile asm error') 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/transformer/constant.ts: -------------------------------------------------------------------------------- 1 | export const accessof = 'accessof' 2 | export const addressof = 'addressof' 3 | export const offsetof = 'offsetof' 4 | export const memcpy = 'memcpy' 5 | export const sizeof = 'sizeof' 6 | export const move = 'move' 7 | export const staticCast = 'static_cast' 8 | export const assert = 'assert' 9 | export const indexOf = 'indexOf' 10 | export const reinterpretCast = 'reinterpret_cast' 11 | export const sharedPtr = 'SharedPtr' 12 | 13 | export const structAccess = 'mapStruct' 14 | export const writePointer = 'writePointer' 15 | export const ctypeEnumWrite = 'CTypeEnumWrite' 16 | export const ctypeEnumRead = 'CTypeEnumRead' 17 | export const definedMetaProperty = 'definedMetaProperty' 18 | 19 | export const symbolStructAddress = 'symbolStructAddress' 20 | export const symbolStruct = 'symbolStruct' 21 | export const symbolStructMaxBaseTypeByteLength = 'symbolStructMaxBaseTypeByteLength' 22 | export const symbolStructLength = 'symbolStructLength' 23 | export const symbolStructKeysMeta = 'symbolStructKeysMeta' 24 | 25 | export const createThreadFromClass = 'createThreadFromClass' 26 | export const createThreadFromFunction = 'createThreadFromFunction' 27 | export const createThreadFromModule = 'createThreadFromModule' 28 | 29 | export const typeArray = 'array' 30 | export const typeBit = 'bit' 31 | export const typePointer = 'pointer' 32 | export const typeSize = 'size' 33 | export const typeAnyptr = 'anyptr' 34 | export const typeNullptr = 'nullptr' 35 | export const typeMultiPointer = 'multiPointer' 36 | export const typeUnion = 'union' 37 | export const typeStruct = 'struct' 38 | export const defined = 'defined' 39 | export const args = 'args' 40 | export const enableArgs = 'enableArgs' 41 | 42 | export const enumPointer = 'typeptr' 43 | 44 | export const tagAsm = 'asm' 45 | export const tagAsm64 = 'asm64' 46 | 47 | export const prototype = 'prototype' 48 | 49 | export const cstruct = 'struct' 50 | export const cunion = 'union' 51 | export const ctype = 'type' 52 | export const cpointer = 'pointer' 53 | export const carray = 'array' 54 | export const cbitField = 'bit' 55 | export const cignore = 'ignore' 56 | export const cinline = 'inline' 57 | export const cdeasync = 'deasync' 58 | 59 | export const make = 'make' 60 | export const unmake = 'unmake' 61 | export const malloc = 'malloc' 62 | export const calloc = 'calloc' 63 | export const realloc = 'realloc' 64 | export const alignedAlloc = 'aligned_alloc' 65 | export const free = 'free' 66 | export const Allocator = 'Allocator' 67 | export const makeSharedPtr = 'make_shared_ptr' 68 | 69 | export const makeSharedPtrImportName = 'makeSharedPtr' 70 | export const smartPointerProperty = ['get', 'reset', 'unique', 'useCount', 'has', 'transferable', 'clone'] 71 | 72 | export const typeProperty = 'zzztype__' 73 | export const levelProperty = 'zzzlevel__' 74 | export const structProperty = 'zzzstruct__' 75 | 76 | export const LINE = '___LINE__' 77 | export const LINE_2 = '__LINE__' 78 | export const FILE = '___FILE__' 79 | export const FILE_2 = '__FILE__' 80 | export const MODULE = '___MODULE__' 81 | export const MODULE_2 = '__MODULE__' 82 | 83 | export const importStar = '__importStar' 84 | export const importDefault = '__importDefault' 85 | export const symbolIsPointer = Symbol('symbolIsPointer') 86 | 87 | export let PACKET_NAME = '' 88 | 89 | export let RootPath = '' 90 | export let InternalPath = '' 91 | export let AllocatorPath = '' 92 | export let makePath = '' 93 | export let unmakePath = '' 94 | export let makeSharedPtrPath = '' 95 | export let atomicsPath = '' 96 | export let sizeofPath = '' 97 | export let definedMetaPropertyPath = '' 98 | export let memoryPath = '' 99 | export let symbolPath = '' 100 | export let structAccessPath = '' 101 | export let ctypeEnumReadPath = '' 102 | export let ctypeEnumWritePath = '' 103 | export let cheapThreadPath = '' 104 | 105 | export function setPacketName(name: string) { 106 | PACKET_NAME = name 107 | RootPath = PACKET_NAME 108 | InternalPath = PACKET_NAME + '/internal' 109 | AllocatorPath = PACKET_NAME + '/heap' 110 | makePath = PACKET_NAME + '/std/make' 111 | unmakePath = PACKET_NAME + '/std/unmake' 112 | makeSharedPtrPath = PACKET_NAME + '/std/smartPtr/SharedPtr' 113 | atomicsPath = PACKET_NAME + '/thread/atomics' 114 | sizeofPath = PACKET_NAME + '/std/sizeof' 115 | definedMetaPropertyPath = PACKET_NAME + '/function/definedMetaProperty' 116 | memoryPath = PACKET_NAME + '/std/memory' 117 | symbolPath = PACKET_NAME + '/symbol' 118 | structAccessPath = PACKET_NAME + '/std/mapStruct' 119 | ctypeEnumReadPath = PACKET_NAME + '/ctypeEnumRead' 120 | ctypeEnumWritePath = PACKET_NAME + '/ctypeEnumWrite' 121 | cheapThreadPath = PACKET_NAME + '/thread/thread' 122 | } 123 | -------------------------------------------------------------------------------- /src/webassembly/runThread.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { ThreadWait } from './thread' 3 | import type WebAssemblyRunner from './WebAssemblyRunner' 4 | 5 | if (defined(ENV_NODE) && !defined(ENV_CJS)) { 6 | // @ts-ignore 7 | import WebAssemblyRunnerClass_ from './WebAssemblyRunner.js' 8 | // @ts-ignore 9 | import { parentPort as parentPort_ } from 'worker_threads' 10 | } 11 | 12 | /* eslint-disable camelcase */ 13 | export default function runThread() { 14 | 15 | let WebAssemblyRunnerClass: typeof WebAssemblyRunner 16 | 17 | if (defined(ENV_NODE)) { 18 | if (defined(ENV_CJS)) { 19 | WebAssemblyRunnerClass = require('./WebAssemblyRunner.js').default 20 | } 21 | else { 22 | WebAssemblyRunnerClass = WebAssemblyRunnerClass_ 23 | } 24 | } 25 | // @ts-ignore 26 | else if (typeof __WebAssemblyRunner__ === 'object') { 27 | // @ts-ignore 28 | WebAssemblyRunnerClass = __WebAssemblyRunner__.__WebAssemblyRunner__ 29 | } 30 | // @ts-ignore 31 | else if (typeof __CHeap_WebAssemblyRunner__ === 'object') { 32 | // @ts-ignore 33 | WebAssemblyRunnerClass = __CHeap_WebAssemblyRunner__.default 34 | } 35 | 36 | let parentPort = self 37 | if (defined(ENV_NODE)) { 38 | if (defined(ENV_CJS)) { 39 | const { parentPort: parentPort_ } = require('worker_threads') 40 | parentPort = parentPort_ 41 | } 42 | else { 43 | parentPort = parentPort_ as any 44 | } 45 | } 46 | 47 | let runner: any 48 | let runnerData: any 49 | let waitData: pointer = nullptr 50 | 51 | const handler = (message: MessageEvent) => { 52 | 53 | const origin = defined(ENV_NODE) ? message : message.data 54 | const type = origin.type 55 | const data = origin.data 56 | 57 | switch (type) { 58 | case 'run': { 59 | parentPort.postMessage({ 60 | type: 'run' 61 | }) 62 | if (self.CHeap && self.CHeap.initThread) { 63 | self.CHeap.initThread(data.cheap).then(() => { 64 | function run() { 65 | self.__SELF_THREAD__ = data.runner.thread 66 | // @ts-ignore 67 | data.runner.options.imports = self.imports 68 | runner = new WebAssemblyRunnerClass(data.runner.resource, data.runner.options) 69 | runner.runAsChild() 70 | WebAssemblyRunnerClass.getTable().get(data.runner.func)(data.runner.args) 71 | runner.destroy() 72 | } 73 | run() 74 | }) 75 | } 76 | break 77 | } 78 | case 'ready': { 79 | runnerData = data.runner 80 | waitData = data.cheap.stackPointer 81 | if (self.CHeap && self.CHeap.initThread) { 82 | self.CHeap.initThread(data.cheap).then(() => { 83 | parentPort.postMessage({ 84 | type: 'ready' 85 | }) 86 | }) 87 | } 88 | break 89 | } 90 | 91 | case 'wait': { 92 | async function run() { 93 | while (true) { 94 | WebAssemblyRunnerClass.mutexLock(addressof(waitData.mutex)) 95 | while (WebAssemblyRunnerClass.readPointer(addressof(waitData.thread)) === nullptr) { 96 | WebAssemblyRunnerClass.condWait(addressof(waitData.cond), addressof(waitData.mutex)) 97 | } 98 | 99 | self.__SELF_THREAD__ = runnerData.options.thread = runnerData.thread = WebAssemblyRunnerClass.readPointer(addressof(waitData.thread)) 100 | 101 | runnerData.func = WebAssemblyRunnerClass.readPointer(addressof(waitData.func)) 102 | runnerData.args = WebAssemblyRunnerClass.readPointer(addressof(waitData.args)) 103 | 104 | runner = new WebAssemblyRunnerClass(runnerData.resource, runnerData.options) 105 | runner.runAsChild() 106 | WebAssemblyRunnerClass.getTable().get(runnerData.func)(runnerData.args) 107 | runner.destroy() 108 | WebAssemblyRunnerClass.writePointer(addressof(waitData.thread), nullptr) 109 | WebAssemblyRunnerClass.mutexUnlock(addressof(waitData.mutex)) 110 | } 111 | } 112 | // @ts-ignore 113 | runnerData.options.imports = self.imports 114 | run() 115 | break 116 | } 117 | 118 | case 'import': { 119 | if (defined(ENV_CSP)) { 120 | // @ts-ignore 121 | importScripts(data.url) 122 | // @ts-ignore 123 | WebAssemblyRunnerClass = __CHeap_WebAssemblyRunner__.default 124 | } 125 | break 126 | } 127 | } 128 | } 129 | 130 | if (defined(ENV_NODE)) { 131 | // @ts-ignore 132 | parentPort.on('message', handler) 133 | } 134 | else { 135 | parentPort.onmessage = handler 136 | } 137 | } 138 | 139 | if (defined(ENV_CSP)) { 140 | runThread() 141 | } 142 | -------------------------------------------------------------------------------- /src/typedef.ts: -------------------------------------------------------------------------------- 1 | import type { Data, TypeArray } from '@libmedia/common' 2 | 3 | export const enum CTypeEnum { 4 | null = 0, 5 | 6 | void, 7 | 8 | uint8, 9 | atomic_uint8, 10 | char, 11 | atomic_char, 12 | uint16, 13 | atomic_uint16, 14 | uint32, 15 | atomic_uint32, 16 | uint64, 17 | 18 | int8, 19 | atomic_int8, 20 | int16, 21 | atomic_int16, 22 | int32, 23 | atomic_int32, 24 | int64, 25 | 26 | float, 27 | double, 28 | pointer, 29 | 30 | atomic_int64, 31 | atomic_uint64, 32 | 33 | bool, 34 | atomic_bool, 35 | size 36 | } 37 | 38 | export const CTypeEnum2Bytes: Record = { 39 | [CTypeEnum.uint8]: 1, 40 | [CTypeEnum.atomic_uint8]: 1, 41 | [CTypeEnum.char]: 1, 42 | [CTypeEnum.atomic_char]: 1, 43 | [CTypeEnum.uint16]: 2, 44 | [CTypeEnum.atomic_uint16]: 2, 45 | [CTypeEnum.uint32]: 4, 46 | [CTypeEnum.atomic_uint32]: 4, 47 | [CTypeEnum.uint64]: 8, 48 | [CTypeEnum.int8]: 1, 49 | [CTypeEnum.atomic_int8]: 1, 50 | [CTypeEnum.int16]: 2, 51 | [CTypeEnum.atomic_int16]: 2, 52 | [CTypeEnum.int32]: 4, 53 | [CTypeEnum.atomic_int32]: 4, 54 | [CTypeEnum.int64]: 8, 55 | [CTypeEnum.float]: 4, 56 | [CTypeEnum.double]: 8, 57 | [CTypeEnum.pointer]: defined(WASM_64) ? 8 : 4, 58 | [CTypeEnum.null]: 4, 59 | [CTypeEnum.void]: 4, 60 | 61 | [CTypeEnum.atomic_uint64]: 8, 62 | [CTypeEnum.atomic_int64]: 8, 63 | 64 | [CTypeEnum.bool]: 1, 65 | [CTypeEnum.atomic_bool]: 1, 66 | [CTypeEnum.size]: defined(WASM_64) ? 8 : 4 67 | } 68 | 69 | export const CTypeEnumPointerShiftMap: Record = { 70 | [CTypeEnum.uint8]: 0, 71 | [CTypeEnum.atomic_uint8]: 0, 72 | [CTypeEnum.char]: 0, 73 | [CTypeEnum.atomic_char]: 0, 74 | [CTypeEnum.uint16]: 1, 75 | [CTypeEnum.atomic_uint16]: 1, 76 | [CTypeEnum.uint32]: 2, 77 | [CTypeEnum.atomic_uint32]: 2, 78 | [CTypeEnum.uint64]: 4, 79 | [CTypeEnum.int8]: 0, 80 | [CTypeEnum.atomic_int8]: 0, 81 | [CTypeEnum.int16]: 1, 82 | [CTypeEnum.atomic_int16]: 1, 83 | [CTypeEnum.int32]: 2, 84 | [CTypeEnum.atomic_int32]: 2, 85 | [CTypeEnum.int64]: 4, 86 | [CTypeEnum.float]: 2, 87 | [CTypeEnum.double]: 4, 88 | [CTypeEnum.pointer]: defined(WASM_64) ? 3 : 2, 89 | [CTypeEnum.void]: 2, 90 | [CTypeEnum.null]: 2, 91 | [CTypeEnum.atomic_uint64]: 4, 92 | [CTypeEnum.atomic_int64]: 4, 93 | 94 | [CTypeEnum.bool]: 0, 95 | [CTypeEnum.atomic_bool]: 0, 96 | [CTypeEnum.size]: defined(WASM_64) ? 3 : 2 97 | } 98 | 99 | export const enum KeyMetaKey { 100 | Type, 101 | Pointer, 102 | PointerLevel, 103 | Array, 104 | ArrayLength, 105 | BitField, 106 | BitFieldLength, 107 | BaseAddressOffset, 108 | BaseBitOffset, 109 | InlineStruct 110 | } 111 | 112 | export type KeyMeta = { 113 | [KeyMetaKey.Type]: CTypeEnum | Struct 114 | [KeyMetaKey.Pointer]: 0 | 1 115 | [KeyMetaKey.PointerLevel]: number 116 | [KeyMetaKey.Array]: 0 | 1 117 | [KeyMetaKey.ArrayLength]: number 118 | [KeyMetaKey.BitField]: 0 | 1 119 | [KeyMetaKey.BitFieldLength]: number 120 | [KeyMetaKey.BaseAddressOffset]: uint32 121 | [KeyMetaKey.BaseBitOffset]: uint32 122 | getTypeMeta?: () => { length: number, maxBaseTypeByteLength: number } 123 | } 124 | 125 | export type Struct = new (init?: Data) => any 126 | export type Union = new (init?: Data) => any 127 | 128 | /* eslint-disable */ 129 | export type CTypeEnum2Type = 130 | T extends CTypeEnum.null 131 | ? void 132 | : T extends CTypeEnum.void 133 | ? void 134 | : T extends CTypeEnum.uint8 135 | ? uint8 136 | : T extends CTypeEnum.atomic_int8 137 | ? atomic_uint8 138 | : T extends CTypeEnum.char 139 | ? char 140 | : T extends CTypeEnum.uint16 141 | ? uint16 142 | : T extends CTypeEnum.atomic_uint16 143 | ? atomic_uint16 144 | : T extends CTypeEnum.uint32 145 | ? uint32 146 | : T extends CTypeEnum.atomic_uint32 147 | ? atomic_uint32 148 | : T extends CTypeEnum.uint64 149 | ? uint64 150 | : T extends CTypeEnum.int8 151 | ? int8 152 | : T extends CTypeEnum.atomic_int8 153 | ? atomic_int8 154 | : T extends CTypeEnum.int16 155 | ? int16 156 | : T extends CTypeEnum.atomic_int16 157 | ? atomic_int16 158 | : T extends CTypeEnum.int32 159 | ? int32 160 | : T extends CTypeEnum.atomic_int32 161 | ? atomic_int32 162 | : T extends CTypeEnum.int64 163 | ? int64 164 | : T extends CTypeEnum.float 165 | ? float 166 | : T extends CTypeEnum.double 167 | ? double 168 | : T extends CTypeEnum.pointer 169 | ? pointer 170 | : T extends CTypeEnum.atomic_int64 171 | ? atomic_int64 172 | : T extends CTypeEnum.atomic_uint64 173 | ? atomic_uint64 174 | : T extends CTypeEnum.bool 175 | ? bool 176 | : T extends CTypeEnum.atomic_bool 177 | ? atomic_bool 178 | : T extends CTypeEnum.size 179 | ? size 180 | : never 181 | /* eslint-enable */ 182 | 183 | export type AtomicsBuffer = Exclude | BigInt64Array | BigUint64Array 184 | -------------------------------------------------------------------------------- /src/allocator/Table.ts: -------------------------------------------------------------------------------- 1 | 2 | interface Node { 3 | pointer: pointer 4 | length: size 5 | free: boolean 6 | } 7 | 8 | export const enum BuiltinTableSlot { 9 | FREE = 1, 10 | MALLOC, 11 | CALLOC, 12 | REALLOC, 13 | ALIGNED_ALLOC, 14 | SLOT_NB 15 | } 16 | 17 | const INIT_SIZE = 10 18 | 19 | export class WebassemblyTable { 20 | 21 | table: WebAssembly.Table 22 | 23 | pointer: pointer 24 | 25 | private nodes: Node[] 26 | 27 | constructor() { 28 | this.table = new WebAssembly.Table({ 29 | initial: reinterpret_cast((BuiltinTableSlot.SLOT_NB + INIT_SIZE) as uint32), 30 | element: 'anyfunc', 31 | // @ts-ignore 32 | address: defined(WASM_64) ? 'i64' : 'i32', 33 | // @ts-ignore 34 | index: defined(WASM_64) ? 'i64' : 'i32' 35 | }) 36 | 37 | this.pointer = static_cast>(BuiltinTableSlot.SLOT_NB as uint32) 38 | this.nodes = [{ 39 | pointer: this.pointer, 40 | length: reinterpret_cast(INIT_SIZE), 41 | free: true 42 | }] 43 | } 44 | 45 | public getPointer() { 46 | return this.pointer 47 | } 48 | 49 | public alloc(count: size) { 50 | 51 | let p = this.findFree(count) 52 | 53 | if (p < 0) { 54 | const last = this.nodes[this.nodes.length - 1] 55 | 56 | const length: size = count - reinterpret_cast(last.free ? last.length : 0) 57 | 58 | this.table.grow(reinterpret_cast(length)) 59 | 60 | if (last.free) { 61 | last.length = last.length + length 62 | } 63 | else { 64 | this.nodes.push({ 65 | pointer: last.pointer + last.length, 66 | length, 67 | free: true 68 | }) 69 | } 70 | 71 | p = this.findFree(count) 72 | } 73 | 74 | const node = this.nodes[p] 75 | 76 | if (node.length > count) { 77 | this.nodes.splice(p + 1, 0, { 78 | pointer: node.pointer + count, 79 | length: node.length - count, 80 | free: true 81 | }) 82 | node.length = count 83 | } 84 | 85 | node.free = false 86 | 87 | return node.pointer 88 | } 89 | 90 | public free(pointer: pointer) { 91 | let p = this.findNode(pointer) 92 | const node = this.nodes[p] 93 | 94 | if (node && !node.free) { 95 | const before = this.nodes[p - 1] 96 | const after = this.nodes[p + 1] 97 | 98 | if (before && before.free) { 99 | if (after && after.free) { 100 | before.length += (node.length + after.length) 101 | this.nodes.splice(p, 2) 102 | } 103 | else { 104 | before.length += node.length 105 | this.nodes.splice(p, 1) 106 | } 107 | } 108 | else { 109 | if (after && after.free) { 110 | node.length += after.length 111 | this.nodes.splice(p + 1, 1) 112 | node.free = true 113 | } 114 | else { 115 | node.free = true 116 | } 117 | } 118 | } 119 | 120 | if (this.nodes.length === 1 && this.nodes[0].free) { 121 | // 当全部 free 之后重新创建新的 Table,之前 WebAssembly 设置的函数引用在 chrome 上没有被回收,会内存泄漏 122 | const table = new WebAssembly.Table({ 123 | initial: reinterpret_cast((BuiltinTableSlot.SLOT_NB + INIT_SIZE) as uint32), 124 | element: 'anyfunc', 125 | // @ts-ignore 126 | address: defined(WASM_64) ? 'i64' : 'i32', 127 | // @ts-ignore 128 | index: defined(WASM_64) ? 'i64' : 'i32' 129 | }) 130 | this.pointer = static_cast>(BuiltinTableSlot.SLOT_NB as uint32) 131 | this.nodes = [{ 132 | pointer: this.pointer, 133 | length: reinterpret_cast(INIT_SIZE), 134 | free: true 135 | }] 136 | for (let i = 1; i < this.pointer; i++) { 137 | table.set(static_cast>(i as uint32), this.table.get(static_cast>(i as uint32))) 138 | } 139 | this.table = table 140 | } 141 | } 142 | 143 | public get any>(index: pointer): T { 144 | return this.table.get(index) 145 | } 146 | 147 | public set any>(index: pointer, value: T) { 148 | if (index < 0 || index >= this.pointer) { 149 | throw new RangeError('index out of bound') 150 | } 151 | this.table.set(index, value) 152 | } 153 | 154 | public inspect() { 155 | return this.nodes 156 | } 157 | 158 | private findFree(length: size) { 159 | let index = -1 160 | for (let i = 0; i < this.nodes.length; i++) { 161 | if (this.nodes[i].length >= length && this.nodes[i].free) { 162 | index = i 163 | break 164 | } 165 | } 166 | return index 167 | } 168 | 169 | private findNode(pointer: pointer) { 170 | let index = -1 171 | for (let i = 0; i < this.nodes.length; i++) { 172 | if (this.nodes[i].pointer === pointer) { 173 | index = i 174 | break 175 | } 176 | } 177 | return index 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/makeSharedPtr.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath, transform2AST } from '../transformer' 4 | import { CTypeEnum } from '../../../typedef' 5 | import { 6 | definedMetaPropertyImport, 7 | symbolImport, 8 | makeSharedPtrImport 9 | } from './snippet' 10 | 11 | describe('makeSharedPtr', () => { 12 | 13 | let input: string 14 | let output: string 15 | 16 | beforeAll(() => { 17 | input = path.join(distPath, './makeSharedPtr.ts') 18 | output = path.join(distPath, './makeSharedPtr_output.ts') 19 | }) 20 | 21 | afterAll(() => { 22 | fs.unlinkSync(input) 23 | fs.rmSync(output, { force: true }) 24 | }) 25 | 26 | test('makeSharedPtr struct', () => { 27 | const source = ` 28 | @struct 29 | class TestA { 30 | a: int8 31 | } 32 | let b = make_shared_ptr() 33 | ` 34 | const target = ` 35 | ${symbolImport} 36 | ${definedMetaPropertyImport} 37 | ${makeSharedPtrImport} 38 | class TestA { 39 | a: int8 40 | static { 41 | const prototype = this.prototype; 42 | const map = new Map(); 43 | map.set("a", { 0: 11, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 }) 44 | definedMetaProperty(prototype, symbolStruct, true) 45 | definedMetaProperty(prototype, symbolStructMaxBaseTypeByteLength, 1) 46 | definedMetaProperty(prototype, symbolStructLength, 1) 47 | definedMetaProperty(prototype, symbolStructKeysMeta, map) 48 | } 49 | } 50 | let b = makeSharedPtr(TestA) 51 | ` 52 | check(source, target, { 53 | input 54 | }) 55 | }) 56 | 57 | test('makeSharedPtr struct init', () => { 58 | const source = ` 59 | @struct 60 | class TestA { 61 | a: int8 62 | } 63 | let b = make_shared_ptr({a: 0}) 64 | ` 65 | const target = ` 66 | ${symbolImport} 67 | ${definedMetaPropertyImport} 68 | ${makeSharedPtrImport} 69 | class TestA { 70 | a: int8 71 | static { 72 | const prototype = this.prototype; 73 | const map = new Map(); 74 | map.set("a", { 0: 11, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0 }) 75 | definedMetaProperty(prototype, symbolStruct, true) 76 | definedMetaProperty(prototype, symbolStructMaxBaseTypeByteLength, 1) 77 | definedMetaProperty(prototype, symbolStructLength, 1) 78 | definedMetaProperty(prototype, symbolStructKeysMeta, map) 79 | } 80 | } 81 | let b = makeSharedPtr({a: 0}, TestA) 82 | ` 83 | check(source, target, { 84 | input 85 | }) 86 | }) 87 | 88 | test('makeSharedPtr builtin type', () => { 89 | const source = ` 90 | let b = make_shared_ptr() 91 | ` 92 | const target = ` 93 | ${makeSharedPtrImport} 94 | let b = makeSharedPtr(${CTypeEnum.uint32}) 95 | ` 96 | check(source, target, { 97 | input 98 | }) 99 | }) 100 | 101 | test('makeSharedPtr builtin type init', () => { 102 | const source = ` 103 | let b = make_shared_ptr(3) 104 | ` 105 | const target = ` 106 | ${makeSharedPtrImport} 107 | let b = makeSharedPtr(3, ${CTypeEnum.uint32}) 108 | ` 109 | check(source, target, { 110 | input 111 | }) 112 | }) 113 | 114 | test('makeSharedPtr no T', () => { 115 | const source = ` 116 | @struct 117 | class TestA { 118 | a: int8 119 | } 120 | let b = make_shared_ptr() 121 | ` 122 | 123 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 124 | 125 | transform2AST(source, { 126 | input 127 | }) 128 | 129 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/invalid typeArguments\n?$/)) 130 | consoleErrorSpy.mockRestore() 131 | }) 132 | 133 | test('makeSharedPtr invalid type', () => { 134 | const source = ` 135 | type a = number 136 | let b = make_shared_ptr() 137 | ` 138 | 139 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 140 | 141 | transform2AST(source, { 142 | input 143 | }) 144 | 145 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/invalid typeArguments, not found struct defined of a or a is not builtin type\n?$/)) 146 | consoleErrorSpy.mockRestore() 147 | }) 148 | 149 | test('makeSharedPtr not struct', () => { 150 | const source = ` 151 | class TestA { 152 | a: int8 153 | } 154 | let b = make_shared_ptr() 155 | ` 156 | 157 | const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() 158 | 159 | transform2AST(source, { 160 | input 161 | }) 162 | 163 | expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringMatching(/invalid typeArguments, not found struct defined of TestA or TestA is not builtin type\n?$/)) 164 | consoleErrorSpy.mockRestore() 165 | }) 166 | }) -------------------------------------------------------------------------------- /src/asm/memory.ts: -------------------------------------------------------------------------------- 1 | import { override as readoverride } from '../ctypeEnumRead' 2 | import { override as writeoverride } from '../ctypeEnumWrite' 3 | 4 | import asm from './memory.asm' 5 | import asm64 from './memory64.asm' 6 | import { CTypeEnum } from '../typedef' 7 | 8 | import { 9 | base64, 10 | logger, 11 | wasm as wasmUtils 12 | } from '@libmedia/common' 13 | 14 | /** 15 | * WebAssembly runtime 实例 16 | */ 17 | export let instance: WebAssembly.Instance 18 | 19 | export function support() { 20 | return !!instance 21 | } 22 | 23 | export function init(memory: WebAssembly.Memory, initial: uint32, maximum: uint32) { 24 | if (defined(DEBUG) && !defined(WASM_64)) { 25 | return 26 | } 27 | try { 28 | const wasm = wasmUtils.setMemoryMeta(base64.base64ToUint8Array(defined(WASM_64) ? asm64 : asm), { 29 | shared: typeof SharedArrayBuffer === 'function' && memory.buffer instanceof SharedArrayBuffer, 30 | initial, 31 | maximum 32 | }) 33 | 34 | instance = new WebAssembly.Instance(new WebAssembly.Module(wasm as BufferSource), { 35 | env: { 36 | memory 37 | } 38 | }) 39 | 40 | readoverride({ 41 | [CTypeEnum.char]: instance.exports.readU8 as any, 42 | [CTypeEnum.atomic_char]: instance.exports.readU8 as any, 43 | [CTypeEnum.uint8]: instance.exports.readU8 as any, 44 | [CTypeEnum.atomic_uint8]: instance.exports.readU8 as any, 45 | [CTypeEnum.uint16]: instance.exports.readU16 as any, 46 | [CTypeEnum.atomic_uint16]: instance.exports.readU16 as any, 47 | [CTypeEnum.uint32]: (pointer: pointer) => { 48 | return (instance.exports.read32 as Function)(pointer) >>> 0 49 | }, 50 | [CTypeEnum.atomic_uint32]: (pointer: pointer) => { 51 | return (instance.exports.read32 as Function)(pointer) >>> 0 52 | }, 53 | [CTypeEnum.uint64]: (pointer: pointer) => { 54 | return BigInt.asUintN(64, (instance.exports.read64 as Function)(pointer)) 55 | }, 56 | [CTypeEnum.atomic_uint64]: (pointer: pointer) => { 57 | return BigInt.asUintN(64, (instance.exports.read64 as Function)(pointer)) 58 | }, 59 | 60 | [CTypeEnum.int8]: instance.exports.read8 as any, 61 | [CTypeEnum.atomic_int8]: instance.exports.read8 as any, 62 | [CTypeEnum.int16]: instance.exports.read16 as any, 63 | [CTypeEnum.atomic_int16]: instance.exports.read16 as any, 64 | [CTypeEnum.int32]: instance.exports.read32 as any, 65 | [CTypeEnum.atomic_int32]: instance.exports.read32 as any, 66 | [CTypeEnum.int64]: instance.exports.read64 as any, 67 | [CTypeEnum.atomic_int64]: instance.exports.read64 as any, 68 | 69 | [CTypeEnum.float]: instance.exports.readf32 as any, 70 | [CTypeEnum.double]: instance.exports.readf64 as any, 71 | 72 | [CTypeEnum.pointer]: (pointer: pointer) => { 73 | if (defined(WASM_64)) { 74 | return reinterpret_cast>(BigInt.asUintN(64, (instance.exports.read64 as Function)(pointer))) 75 | } 76 | else { 77 | return (instance.exports.read32 as Function)(pointer) >>> 0 78 | } 79 | }, 80 | [CTypeEnum.size]: (pointer: pointer) => { 81 | if (defined(WASM_64)) { 82 | return reinterpret_cast>(BigInt.asUintN(64, (instance.exports.read64 as Function)(pointer))) 83 | } 84 | else { 85 | return (instance.exports.read32 as Function)(pointer) >>> 0 86 | } 87 | } 88 | }) 89 | 90 | writeoverride({ 91 | [CTypeEnum.char]: instance.exports.write8 as any, 92 | [CTypeEnum.atomic_char]: instance.exports.write8 as any, 93 | 94 | [CTypeEnum.uint8]: instance.exports.write8 as any, 95 | [CTypeEnum.atomic_uint8]: instance.exports.write8 as any, 96 | [CTypeEnum.uint16]: instance.exports.write16 as any, 97 | [CTypeEnum.atomic_uint16]: instance.exports.write16 as any, 98 | [CTypeEnum.uint32]: instance.exports.write32 as any, 99 | [CTypeEnum.atomic_uint32]: instance.exports.write32 as any, 100 | [CTypeEnum.uint64]: instance.exports.write64 as any, 101 | [CTypeEnum.atomic_uint64]: instance.exports.write64 as any, 102 | 103 | [CTypeEnum.int8]: instance.exports.write8 as any, 104 | [CTypeEnum.atomic_int8]: instance.exports.write8 as any, 105 | [CTypeEnum.int16]: instance.exports.write16 as any, 106 | [CTypeEnum.atomic_int16]: instance.exports.write16 as any, 107 | [CTypeEnum.int32]: instance.exports.write32 as any, 108 | [CTypeEnum.atomic_int32]: instance.exports.write32 as any, 109 | [CTypeEnum.int64]: instance.exports.write64 as any, 110 | [CTypeEnum.atomic_int64]: instance.exports.write64 as any, 111 | 112 | [CTypeEnum.float]: instance.exports.writef32 as any, 113 | [CTypeEnum.double]: instance.exports.writef64 as any, 114 | 115 | [CTypeEnum.pointer]: defined(WASM_64) ? instance.exports.write64 as any : instance.exports.write32 as any, 116 | [CTypeEnum.size]: defined(WASM_64) ? instance.exports.write64 as any : instance.exports.write32 as any, 117 | 'copy': instance.exports.copy as any, 118 | 'fill': instance.exports.fill as any 119 | }) 120 | } 121 | catch (error) { 122 | logger.warn('memory asm not support, cannot use asm memory function') 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/transformer/__test__/case/conditionCompile.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { check, distPath } from '../transformer' 4 | 5 | describe('condition compile', () => { 6 | 7 | let input: string 8 | let output: string 9 | 10 | beforeAll(() => { 11 | input = path.join(distPath, './condition_compile_input.ts') 12 | output = path.join(distPath, './condition_compile_output.ts') 13 | }) 14 | 15 | afterAll(() => { 16 | fs.unlinkSync(input) 17 | fs.rmSync(output, { force: true }) 18 | }) 19 | 20 | test('if: true', () => { 21 | const source = ` 22 | if (defined(A)) { 23 | let a = 0 24 | } 25 | let b = 0 26 | ` 27 | const target = ` 28 | { 29 | let a = 0 30 | } 31 | let b = 0 32 | ` 33 | check(source, target, { 34 | input, 35 | defined: { 36 | A: true 37 | } 38 | }) 39 | }) 40 | 41 | test('if: false', () => { 42 | const source = ` 43 | if (defined(A)) { 44 | let a = 0 45 | } 46 | let b = 0 47 | ` 48 | const target = ` 49 | let b = 0 50 | ` 51 | check(source, target, { 52 | input, 53 | defined: { 54 | A: false 55 | } 56 | }) 57 | }) 58 | 59 | test('if elseif else: if', () => { 60 | const source = ` 61 | if (defined(A)) { 62 | let a = 0 63 | } 64 | else if (defined(B)) { 65 | let b = 0 66 | } 67 | else { 68 | let c = 0 69 | } 70 | let d = 0 71 | ` 72 | const target = ` 73 | { 74 | let a = 0 75 | } 76 | let d = 0 77 | ` 78 | check(source, target, { 79 | input, 80 | defined: { 81 | A: true, 82 | B: false 83 | } 84 | }) 85 | }) 86 | 87 | test('if elseif else: elseif', () => { 88 | const source = ` 89 | if (defined(A)) { 90 | let a = 0 91 | } 92 | else if (defined(B)) { 93 | let b = 0 94 | } 95 | else { 96 | let c = 0 97 | } 98 | let d = 0 99 | ` 100 | const target = ` 101 | { 102 | let b = 0 103 | } 104 | let d = 0 105 | ` 106 | check(source, target, { 107 | input, 108 | defined: { 109 | A: false, 110 | B: true 111 | } 112 | }) 113 | }) 114 | 115 | test('if elseif else: else', () => { 116 | const source = ` 117 | if (defined(A)) { 118 | let a = 0 119 | } 120 | else if (defined(B)) { 121 | let b = 0 122 | } 123 | else { 124 | let c = 0 125 | } 126 | let d = 0 127 | ` 128 | const target = ` 129 | { 130 | let c = 0 131 | } 132 | let d = 0 133 | ` 134 | check(source, target, { 135 | input, 136 | defined: { 137 | A: false, 138 | B: false 139 | } 140 | }) 141 | }) 142 | 143 | test('if: defined() && defined()', () => { 144 | const source = ` 145 | if (defined(A) && defined(B)) { 146 | let a = 0 147 | } 148 | let b = 0 149 | ` 150 | const target = ` 151 | { 152 | let a = 0 153 | } 154 | let b = 0 155 | ` 156 | check(source, target, { 157 | input, 158 | defined: { 159 | A: true, 160 | B: true 161 | } 162 | }) 163 | }) 164 | 165 | test('if: defined() && true', () => { 166 | const source = ` 167 | if (defined(A) && true) { 168 | let a = 0 169 | } 170 | let b = 0 171 | ` 172 | const target = ` 173 | { 174 | let a = 0 175 | } 176 | let b = 0 177 | ` 178 | check(source, target, { 179 | input, 180 | defined: { 181 | A: true 182 | } 183 | }) 184 | }) 185 | 186 | test('if: defined() && 1', () => { 187 | const source = ` 188 | if (defined(A) && 1) { 189 | let a = 0 190 | } 191 | let b = 0 192 | ` 193 | const target = ` 194 | if (true && 1) { 195 | let a = 0 196 | } 197 | let b = 0 198 | ` 199 | check(source, target, { 200 | input, 201 | defined: { 202 | A: true 203 | } 204 | }) 205 | }) 206 | 207 | test('if: defined() && identifier', () => { 208 | const source = ` 209 | let f = 0 210 | if (defined(A) && f) { 211 | let a = 0 212 | } 213 | let b = 0 214 | ` 215 | const target = ` 216 | let f = 0 217 | if (false && f) { 218 | let a = 0 219 | } 220 | let b = 0 221 | ` 222 | check(source, target, { 223 | input, 224 | defined: { 225 | A: false 226 | } 227 | }) 228 | }) 229 | 230 | test('condition: defined() when true', () => { 231 | const source = ` 232 | let f = defined(A) ? 1 : 2 233 | ` 234 | const target = ` 235 | let f = 1 236 | ` 237 | check(source, target, { 238 | input, 239 | defined: { 240 | A: true 241 | } 242 | }) 243 | }) 244 | 245 | test('condition: defined() when false', () => { 246 | const source = ` 247 | let f = defined(A) ? 1 : 2 248 | ` 249 | const target = ` 250 | let f = 2 251 | ` 252 | check(source, target, { 253 | input, 254 | defined: { 255 | A: false 256 | } 257 | }) 258 | }) 259 | }) -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | export const enum CHeapError { 2 | REQUEST_ERROR = -(1 << 21) 3 | } 4 | 5 | export const enum POSIXError { 6 | EPERM = 1, /* Not super-user */ 7 | ENOENT, /* No such file or directory */ 8 | ESRCH, /* No such process */ 9 | EINTR, /* Interrupted system call */ 10 | EIO, /* I/O error */ 11 | ENXIO, /* No such device or address */ 12 | E2BIG, /* Arg list too long */ 13 | ENOEXEC, /* Exec format error */ 14 | EBADF, /* Bad file number */ 15 | ECHILD, /* No children */ 16 | EAGAIN, /* No more processes */ 17 | ENOMEM, /* Not enough core */ 18 | EACCES, /* Permission denied */ 19 | EFAULT, /* Bad address */ 20 | ENOTBLK, /* Block device required */ 21 | EBUSY, /* Mount device busy */ 22 | EEXIST, /* File exists */ 23 | EXDEV, /* Cross-device link */ 24 | ENODEV, /* No such device */ 25 | ENOTDIR, /* Not a directory */ 26 | EISDIR, /* Is a directory */ 27 | EINVAL, /* Invalid argument */ 28 | ENFILE, /* Too many open files in system */ 29 | EMFILE, /* Too many open files */ 30 | ENOTTY, /* Not a typewriter */ 31 | ETXTBSY, /* Text file busy */ 32 | EFBIG, /* File too large */ 33 | ENOSPC, /* No space left on device */ 34 | ESPIPE, /* Illegal seek */ 35 | EROFS, /* Read only file system */ 36 | EMLINK, /* Too many links */ 37 | EPIPE, /* Broken pipe */ 38 | EDOM, /* Math arg out of domain of func */ 39 | ERANGE, /* Math result not representable */ 40 | ENOMSG, /* No message of desired type */ 41 | EIDRM, /* Identifier removed */ 42 | ECHRNG, /* Channel number out of range */ 43 | EL2NSYNC, /* Level 2 not synchronized */ 44 | EL3HLT, /* Level 3 halted */ 45 | EL3RST, /* Level 3 reset */ 46 | ELNRNG, /* Link number out of range */ 47 | EUNATCH, /* Protocol driver not attached */ 48 | ENOCSI, /* No CSI structure available */ 49 | EL2HLT, /* Level 2 halted */ 50 | EDEADLK, /* Deadlock condition */ 51 | ENOLCK, /* No record locks available */ 52 | EBADE, /* Invalid exchange */ 53 | EBADR, /* Invalid request descriptor */ 54 | EXFULL, /* Exchange full */ 55 | ENOANO, /* No anode */ 56 | EBADRQC, /* Invalid request code */ 57 | EBADSLT, /* Invalid slot */ 58 | EDEADLOCK, /* File locking deadlock error */ 59 | EBFONT, /* Bad font file fmt */ 60 | ENOSTR, /* Device not a stream */ 61 | ENODATA, /* No data (for no delay io) */ 62 | ETIME, /* Timer expired */ 63 | ENOSR, /* Out of streams resources */ 64 | ENONET, /* Machine is not on the network */ 65 | ENOPKG, /* Package not installed */ 66 | EREMOTE, /* The object is remote */ 67 | ENOLINK, /* The link has been severed */ 68 | EADV, /* Advertise error */ 69 | ESRMNT, /* Srmount error */ 70 | ECOMM, /* Communication error on send */ 71 | EPROTO, /* Protocol error */ 72 | EMULTIHOP, /* Multihop attempted */ 73 | ELBIN, /* Inode is remote (not really error) */ 74 | EDOTDOT, /* Cross mount point (not really error) */ 75 | EBADMSG, /* Trying to read unreadable message */ 76 | EFTYPE, /* Inappropriate file type or format */ 77 | ENOTUNIQ, /* Given log. name not unique */ 78 | EBADFD, /* f.d. invalid for this operation */ 79 | EREMCHG, /* Remote address changed */ 80 | ELIBACC, /* Can't access a needed shared lib */ 81 | ELIBBAD, /* Accessing a corrupted shared lib */ 82 | ELIBSCN, /* .lib section in a.out corrupted */ 83 | ELIBMAX, /* Attempting to link in too many libs */ 84 | ELIBEXEC, /* Attempting to exec a shared library */ 85 | ENOSYS, /* Function not implemented */ 86 | ENMFILE, /* No more files */ 87 | ENOTEMPTY, /* Directory not empty */ 88 | ENAMETOOLONG, /* File or path name too long */ 89 | ELOOP, /* Too many symbolic links */ 90 | EOPNOTSUPP, /* Operation not supported on transport endpoint */ 91 | EPFNOSUPPORT, /* Protocol family not supported */ 92 | ECONNRESET, /* Connection reset by peer */ 93 | ENOBUFS, /* No buffer space available */ 94 | EAFNOSUPPORT, /* Address family not supported by protocol family */ 95 | EPROTOTYPE, /* Protocol wrong type for socket */ 96 | ENOTSOCK, /* Socket operation on non-socket */ 97 | ENOPROTOOPT, /* Protocol not available */ 98 | ESHUTDOWN, /* Can't send after socket shutdown */ 99 | ECONNREFUSED, /* Connection refused */ 100 | EADDRINUSE, /* Address already in use */ 101 | ECONNABORTED, /* Connection aborted */ 102 | ENETUNREACH, /* Network is unreachable */ 103 | ENETDOWN, /* Network interface is not configured */ 104 | ETIMEDOUT, /* Connection timed out */ 105 | EHOSTDOWN, /* Host is down */ 106 | EHOSTUNREACH, /* Host is unreachable */ 107 | EINPROGRESS, /* Connection already in progress */ 108 | EALREADY, /* Socket already connected */ 109 | EDESTADDRREQ, /* Destination address required */ 110 | EMSGSIZE, /* Message too long */ 111 | EPROTONOSUPPORT, /* Unknown protocol */ 112 | ESOCKTNOSUPPORT, /* Socket type not supported */ 113 | EADDRNOTAVAIL, /* Address not available */ 114 | ENETRESET, 115 | EISCONN, /* Socket is already connected */ 116 | ENOTCONN, /* Socket is not connected */ 117 | ETOOMANYREFS, 118 | EPROCLIM, 119 | EUSERS, 120 | EDQUOT, 121 | ESTALE, 122 | ENOTSUP, /* Not supported */ 123 | ENOMEDIUM, /* No medium (in tape drive) */ 124 | ENOSHARE, /* No such host or network path */ 125 | ECASECLASH, /* Filename exists with different case */ 126 | EILSEQ, 127 | EOVERFLOW, /* Value too large for defined data type */ 128 | EWOULDBLOCK = EAGAIN, /* Operation would block */ 129 | __ELASTERROR = 2000 /* Users can add values starting here */ 130 | } 131 | -------------------------------------------------------------------------------- /src/thread/atomics.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export type AtomicType2Shift = 3 | T extends atomic_char 4 | ? 0 5 | : T extends atomic_int16 6 | ? 1 7 | : T extends atomic_int32 8 | ? 2 9 | : T extends atomic_int8 10 | ? 0 11 | : T extends atomic_uint8 12 | ? 0 13 | : T extends atomic_uint16 14 | ? 1 15 | : T extends atomic_uint32 16 | ? 2 17 | : T extends atomic_uint64 18 | ? 4 19 | : T extends atomic_int64 20 | ? 4 21 | : T extends atomic_bool 22 | ? 0 23 | : never 24 | /* eslint-enable */ 25 | 26 | type AtomicType2CTypeEnum = 27 | T extends atomic_bool 28 | ? atomic_int8 29 | : T 30 | 31 | /** 32 | * 给定的值加到指定位置上 33 | * 34 | * 返回该位置的旧值 35 | * 36 | */ 37 | export let add: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 38 | 39 | /** 40 | * 给定的值与指定位置上的值相减 41 | * 42 | * 返回该位置的旧值 43 | * 44 | */ 45 | export let sub: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 46 | 47 | /** 48 | * 给定的值与指定位置上的值进行与运算 49 | * 50 | * 返回该位置的旧值 51 | * 52 | */ 53 | export let and: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 54 | 55 | /** 56 | * 给定的值与指定位置上的值进行或运算 57 | * 58 | * 返回该位置的旧值 59 | * 60 | */ 61 | export let or: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 62 | 63 | /** 64 | * 给定的值与指定位置上的值进行异或运算 65 | * 66 | * 返回该位置的旧值 67 | * 68 | */ 69 | export let xor: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 70 | 71 | /** 72 | * 给定的值存在给定位置上 73 | * 74 | * 返回该位置的旧值 75 | * 76 | */ 77 | export let store: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 78 | 79 | /** 80 | * 读取给定位置上的值 81 | * 82 | * 返回该位置的旧值 83 | * 84 | */ 85 | export let load: , AtomicType2Shift]>(address: pointer) => AtomicType2Type 86 | 87 | /** 88 | * 如果指定位置的值与给定的值相等,则将其更新为新的值,并返回该元素原先的值 89 | * 90 | * 返回该位置的旧值 91 | * 92 | */ 93 | export let compareExchange: , AtomicType2Shift]>( 94 | address: pointer, 95 | expectedValue: AtomicType2Type, 96 | replacementValue: AtomicType2Type 97 | ) => AtomicType2Type 98 | /** 99 | * 将指定位置的值更新为给定的值,并返回该元素更新前的值。 100 | * 101 | * 返回该位置的旧值 102 | * 103 | */ 104 | export let exchange: , AtomicType2Shift]>(address: pointer, value: AtomicType2Type) => AtomicType2Type 105 | 106 | 107 | /** 108 | * 唤醒等待队列中正在指定位置上等待的线程。返回值为成功唤醒的线程数量。 109 | * 110 | * 返回被唤醒的代理的数量 0 将不会唤醒任何线程 111 | * 112 | */ 113 | export let notify: (address: pointer, count: uint32) => uint32 114 | 115 | /** 116 | * 检测指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒 117 | * 118 | * 0 "ok"、1 "not-equal" 119 | * 120 | */ 121 | export let wait: (address: pointer, value: int32) => 0 | 1 | 2 122 | 123 | /** 124 | * 检测指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时(毫秒) 125 | * 126 | * 0 "ok"、1 "not-equal" 或 2 "time-out" 127 | * 128 | */ 129 | export let waitTimeout: (address: pointer, value: int32, timeout: int32) => 0 | 1 | 2 130 | 131 | /** 132 | * 检测指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒 133 | * 134 | * 异步非阻塞,适合在主线程上使用 135 | * 136 | * 0 "ok"、1 "not-equal" 137 | * 138 | */ 139 | export let waitAsync: (address: pointer, value: int32) => Promise<0 | 1 | 2> 140 | /** 141 | * 检测指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时 142 | * 143 | * 异步非阻塞,适合在主线程上使用 144 | * 145 | * 0 "ok"、1 "not-equal" 或 2 "time-out" 146 | * 147 | */ 148 | export let waitTimeoutAsync: (address: pointer, value: int32, timeout: int32) => Promise<0 | 1 | 2> 149 | 150 | export function override(funcs: Partial<{ 151 | add: typeof add 152 | sub: typeof sub 153 | and: typeof and 154 | or: typeof or 155 | xor: typeof xor 156 | store: typeof store 157 | load: typeof load 158 | compareExchange: typeof compareExchange 159 | exchange: typeof exchange 160 | notify: typeof notify 161 | wait: typeof wait 162 | waitTimeout: typeof waitTimeout 163 | waitAsync: typeof waitAsync 164 | waitTimeoutAsync: typeof waitTimeoutAsync 165 | }>) { 166 | if (funcs.add) { 167 | add = funcs.add 168 | } 169 | if (funcs.sub) { 170 | sub = funcs.sub 171 | } 172 | if (funcs.and) { 173 | and = funcs.and 174 | } 175 | if (funcs.or) { 176 | or = funcs.or 177 | } 178 | if (funcs.xor) { 179 | xor = funcs.xor 180 | } 181 | if (funcs.store) { 182 | store = funcs.store 183 | } 184 | if (funcs.load) { 185 | load = funcs.load 186 | } 187 | if (funcs.compareExchange) { 188 | compareExchange = funcs.compareExchange 189 | } 190 | if (funcs.exchange) { 191 | exchange = funcs.exchange 192 | } 193 | if (funcs.notify) { 194 | notify = funcs.notify 195 | } 196 | if (funcs.wait) { 197 | wait = funcs.wait 198 | } 199 | if (funcs.waitTimeout) { 200 | waitTimeout = funcs.waitTimeout 201 | } 202 | if (funcs.waitAsync) { 203 | waitAsync = funcs.waitAsync 204 | } 205 | if (funcs.waitTimeoutAsync) { 206 | waitTimeoutAsync = funcs.waitTimeoutAsync 207 | } 208 | } 209 | --------------------------------------------------------------------------------