diff --git a/lib/builtin/lib.type.d.ts b/lib/builtin/lib.type.d.ts index 31c62540..639d5ee5 100644 --- a/lib/builtin/lib.type.d.ts +++ b/lib/builtin/lib.type.d.ts @@ -304,20 +304,21 @@ declare function setTimeout( declare function clearTimeout(timerid: number): void; interface ArrayBuffer { - readonly byteLength: number; + readonly backing_store: anyref; + readonly byteLength: i32; slice(begin?: number, end?: number): ArrayBuffer; } interface ArrayBufferConstructor { - new (byteLength: number): ArrayBuffer; + new (byteLength: i32): ArrayBuffer; isView(arg: any): arg is ArrayBufferView; } declare var ArrayBuffer: ArrayBufferConstructor; interface DataView { readonly buffer: ArrayBuffer; - readonly byteLength: number; - readonly byteOffset: number; + readonly byteLength: i32; + readonly byteOffset: i32; getFloat32(byteOffset: number, littleEndian?: boolean): number; getFloat64(byteOffset: number, littleEndian?: boolean): number; diff --git a/src/backend/binaryen/index.ts b/src/backend/binaryen/index.ts index 3217453e..a4df2257 100644 --- a/src/backend/binaryen/index.ts +++ b/src/backend/binaryen/index.ts @@ -6,7 +6,13 @@ import binaryen from 'binaryen'; import * as binaryenCAPI from './glue/binaryen.js'; import { arrayToPtr, emptyStructType } from './glue/transform.js'; -import { PredefinedTypeId, Stack } from '../../utils.js'; +import { + PredefinedTypeId, + Stack, + isExportComment, + isImportComment, + isNativeSignatureComment, +} from '../../utils.js'; import { importAnyLibAPI, importInfcLibAPI, @@ -38,6 +44,7 @@ import { FunctionOwnKind, IfNode, ModuleNode, + NativeSignature, SemanticsNode, SwitchNode, TryNode, @@ -61,6 +68,7 @@ import { SourceMapLoc, BackendLocalVar, UtilFuncs, + NativeSignatureConversion, } from './utils.js'; import { MemberDescription, @@ -507,16 +515,20 @@ export class WASMGen extends Ts2wasmBackend { const paramWASMTypes = this.wasmTypeComp.getWASMFuncParamTypes(tsFuncType); const returnType = tsFuncType.returnType; - const returnWASMType = this.wasmTypeComp.getWASMValueType(returnType); + let returnWASMType = this.wasmTypeComp.getWASMValueType(returnType); const oriParamWasmTypes = this.wasmTypeComp.getWASMFuncOriParamTypes(tsFuncType); /* generate import function name */ const levelNames = func.name.split(BuiltinNames.moduleDelimiter); - let importName = levelNames[levelNames.length - 1]; + let funcPureName = levelNames[levelNames.length - 1]; if ((func.ownKind & FunctionOwnKind.METHOD) !== 0) { - importName = `${levelNames[levelNames.length - 2]}_${importName}`; + funcPureName = `${ + levelNames[levelNames.length - 2] + }_${funcPureName}`; } + let importName = funcPureName; + let exportName = funcPureName; /** declare functions */ if ((func.ownKind & FunctionOwnKind.DECLARE) !== 0) { @@ -525,45 +537,75 @@ export class WASMGen extends Ts2wasmBackend { /* For method, just skip the @context */ skipEnvParamLen = BuiltinNames.envParamLen - 1; } - const importParamWASMTypes = paramWASMTypes.slice(skipEnvParamLen); + let calledParamValueRefs: binaryen.ExpressionRef[] = []; + for (let i = skipEnvParamLen; i < paramWASMTypes.length; i++) { + calledParamValueRefs.push( + this.module.local.get(i, paramWASMTypes[i]), + ); + } const internalFuncName = `${func.name}${BuiltinNames.declareSuffix}`; + let moduleName = BuiltinNames.externalModuleName; + let importParamTypeRefs = paramWASMTypes.slice(skipEnvParamLen); + const innerOpStmts: binaryen.ExpressionRef[] = []; + const vars: binaryen.Type[] = []; + for (let comment of func.comments) { + if (isImportComment(comment)) { + moduleName = comment.moduleName; + importName = comment.funcName; + } else if (isNativeSignatureComment(comment)) { + comment = comment as NativeSignature; + importParamTypeRefs = []; + calledParamValueRefs = []; + returnWASMType = this.wasmTypeComp.getWASMValueType( + comment.returnType, + ); + for (const paramType of comment.paramTypes) { + const paramTypeRef = + this.wasmTypeComp.getWASMValueType(paramType); + importParamTypeRefs.push(paramTypeRef); + } + FunctionalFuncs.parseNativeSignature( + this.module, + innerOpStmts, + tsFuncType.argumentsType, + paramWASMTypes.slice(skipEnvParamLen), + comment.paramTypes, + skipEnvParamLen, + calledParamValueRefs, + vars, + true, + ); + } else if (isExportComment(comment)) { + exportName = comment.exportName; + } + } this.module.addFunctionImport( internalFuncName, - BuiltinNames.externalModuleName, + moduleName, importName, - binaryen.createType(importParamWASMTypes), + binaryen.createType(importParamTypeRefs), returnWASMType, ); - /* use wrappered func to invoke the orignal func */ - const oriParamWasmValues: binaryen.ExpressionRef[] = []; - for (let i = 0; i < importParamWASMTypes.length; i++) { - oriParamWasmValues.push( - this.module.local.get( - i + skipEnvParamLen, - importParamWASMTypes[i], - ), - ); - } - let innerOp: binaryen.ExpressionRef; const callOp = this.module.call( internalFuncName, - oriParamWasmValues, + calledParamValueRefs, returnWASMType, ); + // TODO: ts's return type is not same with native signature's return type. if (returnType.kind !== ValueTypeKind.VOID) { - innerOp = this.module.return(callOp); + innerOpStmts.push(this.module.return(callOp)); } else { - innerOp = callOp; + innerOpStmts.push(callOp); } this.module.addFunction( func.name, binaryen.createType(paramWASMTypes), returnWASMType, - [], - this.module.block(null, [innerOp], returnWASMType), + vars, + this.module.block(null, innerOpStmts, returnWASMType), ); if ((func.ownKind & FunctionOwnKind.EXPORT) !== 0) { - this.module.addFunctionExport(internalFuncName, importName); + this.module.addFunctionExport(internalFuncName, exportName); } return; } @@ -777,67 +819,89 @@ export class WASMGen extends Ts2wasmBackend { } this.currentFuncCtx.localVarIdxNameMap.clear(); - /** wrapped functions */ + /** wrapped export functions */ if ( (func.ownKind & (FunctionOwnKind.EXPORT | FunctionOwnKind.DEFAULT)) === - (FunctionOwnKind.EXPORT | FunctionOwnKind.DEFAULT) && - func.isInEnterScope + (FunctionOwnKind.EXPORT | FunctionOwnKind.DEFAULT) ) { - const wrapperName = importName.concat(BuiltinNames.wrapperSuffix); - let idx = 0; - let oriParamWasmValues: binaryen.ExpressionRef[] = []; - if (func.parameters) { - oriParamWasmValues = func.parameters.map((param) => { - return this.module.local.get( - idx++, - this.wasmTypeComp.getWASMValueType(param.type), + if ( + func.isInEnterScope || + func.comments.some((comment) => { + return isExportComment(comment); + }) + ) { + const exportWrapperName = funcPureName.concat( + BuiltinNames.wrapperSuffix, + ); + let exportName = funcPureName; + let exportParamTypeRefs = oriParamWasmTypes; + const calledParamValueRefs: binaryen.ExpressionRef[] = []; + for (let i = 0; i < tsFuncType.envParamLen; i++) { + calledParamValueRefs.push(this.emptyRef); + } + for (let i = 0; i < exportParamTypeRefs.length; i++) { + calledParamValueRefs.push( + this.module.local.get(i, exportParamTypeRefs[i]), ); - }) as unknown as binaryen.ExpressionRef[]; - } - /* add init statements */ - const functionStmts: binaryen.ExpressionRef[] = []; - /* call globalInitFunc */ - functionStmts.push( - this.module.call( - BuiltinNames.globalInitFuncName, - [], - binaryen.none, - ), - ); - const wrapperCallArgs: binaryen.ExpressionRef[] = []; - for (let i = 0; i < tsFuncType.envParamLen; i++) { - wrapperCallArgs.push(this.emptyRef); - } - const targetCall = this.module.call( - func.name, - wrapperCallArgs.concat(oriParamWasmValues), - returnWASMType, - ); - const isReturn = returnWASMType === binaryen.none ? false : true; - functionStmts.push( - isReturn ? this.module.local.set(idx, targetCall) : targetCall, - ); - - /* set return value */ - const functionVars: binaryen.ExpressionRef[] = []; - if (isReturn) { - functionStmts.push( - this.module.return( - this.module.local.get(idx, returnWASMType), + } + const innerOpStmts: binaryen.ExpressionRef[] = []; + const vars: binaryen.Type[] = []; + for (let comment of func.comments) { + if (isExportComment(comment)) { + exportName = comment.exportName; + } else if (isNativeSignatureComment(comment)) { + comment = comment as NativeSignature; + exportParamTypeRefs = []; + calledParamValueRefs.splice(tsFuncType.envParamLen); + returnWASMType = this.wasmTypeComp.getWASMValueType( + comment.returnType, + ); + for (const paramType of comment.paramTypes) { + const paramTypeRef = + this.wasmTypeComp.getWASMValueType(paramType); + exportParamTypeRefs.push(paramTypeRef); + } + FunctionalFuncs.parseNativeSignature( + this.module, + innerOpStmts, + comment.paramTypes, + exportParamTypeRefs, + tsFuncType.argumentsType, + tsFuncType.envParamLen, + calledParamValueRefs, + vars, + false, + ); + } + } + innerOpStmts.push( + this.module.call( + BuiltinNames.globalInitFuncName, + [], + binaryen.none, ), ); - functionVars.push(returnWASMType); + const callOp = this.module.call( + func.name, + calledParamValueRefs, + returnWASMType, + ); + // TODO: ts's return type is not same with native signature's return type. + if (returnType.kind !== ValueTypeKind.VOID) { + innerOpStmts.push(this.module.return(callOp)); + } else { + innerOpStmts.push(callOp); + } + this.module.addFunction( + exportWrapperName, + binaryen.createType(exportParamTypeRefs), + returnWASMType, + vars, + this.module.block(null, innerOpStmts), + ); + this.module.addFunctionExport(exportWrapperName, exportName); } - - this.module.addFunction( - wrapperName, - binaryen.createType(oriParamWasmTypes), - returnWASMType, - functionVars, - this.module.block(null, functionStmts), - ); - this.module.addFunctionExport(wrapperName, importName); } this.generatedFuncNames.push(func.name); diff --git a/src/backend/binaryen/lib/init_builtin_api.ts b/src/backend/binaryen/lib/init_builtin_api.ts index 652c9c4d..254b1ded 100644 --- a/src/backend/binaryen/lib/init_builtin_api.ts +++ b/src/backend/binaryen/lib/init_builtin_api.ts @@ -3794,32 +3794,17 @@ function arrayBufferConstructor(module: binaryen.Module) { const this_Idx = 1; const byteLength_Idx = 2; - /* values */ - /* workaround: in type.d.ts, type is number, not wasmType i32 */ - const byteLength_i32_Idx = 3; - const stmts: binaryen.ExpressionRef[] = []; - stmts.push( - module.local.set( - byteLength_i32_Idx, - FunctionalFuncs.convertTypeToI32( - module, - module.local.get(byteLength_Idx, binaryen.f64), - ), - ), - ); const i8Array = binaryenCAPI._BinaryenArrayNew( module.ptr, i8ArrayTypeInfo.heapTypeRef, - module.local.get(byteLength_i32_Idx, binaryen.i32), + module.local.get(byteLength_Idx, binaryen.i32), module.i32.const(0), ); const arrayBufferStruct = binaryenCAPI._BinaryenStructNew( module.ptr, - arrayToPtr([ - i8Array, - module.local.get(byteLength_i32_Idx, binaryen.i32), - ]).ptr, + arrayToPtr([i8Array, module.local.get(byteLength_Idx, binaryen.i32)]) + .ptr, 2, arrayBufferTypeInfo.heapTypeRef, ); @@ -3862,12 +3847,10 @@ function dataViewConstructor(module: binaryen.Module) { anyref, dyntype.dyntype_is_undefined, ); - const value = module.i32.trunc_s_sat.f64( - module.call( - dyntype.dyntype_to_number, - [FunctionalFuncs.getDynContextRef(module), anyref], - binaryen.f64, - ), + const value = FunctionalFuncs.unboxAnyToBase( + module, + anyref, + ValueTypeKind.INT, ); return module.if( isUndefined, @@ -5816,10 +5799,10 @@ export function callBuiltInAPIs(module: binaryen.Module) { binaryen.createType([ emptyStructType.typeRef, emptyStructType.typeRef, - binaryen.f64, + binaryen.i32, ]), arrayBufferTypeInfo.typeRef, - [binaryen.i32], + [], arrayBufferConstructor(module), ); module.addFunctionImport( diff --git a/src/backend/binaryen/utils.ts b/src/backend/binaryen/utils.ts index b1042a7c..ae8f0c2d 100644 --- a/src/backend/binaryen/utils.ts +++ b/src/backend/binaryen/utils.ts @@ -9,7 +9,10 @@ import ts from 'typescript'; import { BuiltinNames } from '../../../lib/builtin/builtin_name.js'; import { UnimplementError } from '../../error.js'; import { dyntype, structdyn } from './lib/dyntype/utils.js'; -import { SemanticsKind } from '../../semantics/semantics_nodes.js'; +import { + NativeSignature, + SemanticsKind, +} from '../../semantics/semantics_nodes.js'; import { EnumType, ObjectType, @@ -34,6 +37,7 @@ import { stringArrayTypeInfo, stringArrayStructTypeInfo, stringrefArrayStructTypeInfo, + arrayBufferTypeInfo, } from './glue/packType.js'; import { PredefinedTypeId, @@ -48,6 +52,7 @@ import { import { ObjectDescriptionType } from '../../semantics/runtime.js'; import { getConfig } from '../../../config/config_mgr.js'; import { memoryAlignment } from './memory.js'; +import { assert } from 'console'; /** typeof an any type object */ export const enum DynType { @@ -121,6 +126,13 @@ export interface SourceMapLoc { ref: binaryen.ExpressionRef; } +export const enum NativeSignatureConversion { + INVALID, + ARRAYBUFFER_TO_I32, + I32_TO_ARRAYBUFFER, + I32_TO_I32, +} + export const META_FLAG_MASK = 0x0000000f; export const META_INDEX_MASK = 0xfffffff0; @@ -2016,4 +2028,272 @@ export namespace FunctionalFuncs { ); return ifPropTypeIdCompatible; } + + export function copyArrayBufferToLinearMemory( + module: binaryen.Module, + arrayBufferRef: binaryen.ExpressionRef, + startIdx: number, + calledParamValueRefs: binaryen.ExpressionRef[], + vars: binaryen.Type[], + ) { + const stmts: binaryen.ExpressionRef[] = []; + // TODO: allocate linear memory through malloc + stmts.push(module.local.set(startIdx, module.i32.const(0))); + vars.push(binaryen.i32); + const loopIndexValue = module.local.get(startIdx, binaryen.i32); + const codesArray = binaryenCAPI._BinaryenStructGet( + module.ptr, + 0, + arrayBufferRef, + arrayBufferTypeInfo.typeRef, + false, + ); + const codeLen = binaryenCAPI._BinaryenStructGet( + module.ptr, + 1, + arrayBufferRef, + arrayBufferTypeInfo.typeRef, + false, + ); + /* Put elem in linear memory */ + const loopLabel = 'for_label'; + const loopCond = module.i32.lt_s(loopIndexValue, codeLen); + const loopIncrementor = module.local.set( + startIdx, + module.i32.add(loopIndexValue, module.i32.const(1)), + ); + const loopBody: binaryen.ExpressionRef[] = []; + loopBody.push( + module.i32.store8( + 0, + 1, + module.i32.add( + module.i32.const(BuiltinNames.memoryReserveOffset), + loopIndexValue, + ), + binaryenCAPI._BinaryenArrayGet( + module.ptr, + codesArray, + loopIndexValue, + arrayBufferTypeInfo.typeRef, + false, + ), + ), + ); + const flattenLoop: FlattenLoop = { + label: loopLabel, + condition: loopCond, + statements: module.block(null, loopBody), + incrementor: loopIncrementor, + }; + stmts.push( + module.loop( + loopLabel, + FunctionalFuncs.flattenLoopStatement( + module, + flattenLoop, + SemanticsKind.FOR, + ), + ), + ); + stmts.push( + module.local.set( + startIdx, + module.i32.const(BuiltinNames.memoryReserveOffset), + ), + ); + calledParamValueRefs.push(module.local.get(startIdx, binaryen.i32)); + return module.block(null, stmts); + } + + export function copyLinearMemoryToArrayBuffer( + module: binaryen.Module, + offsetValueRef: binaryen.ExpressionRef, + lengthRef: binaryen.ExpressionRef, + startIdx: number, + calledParamValueRefs: binaryen.ExpressionRef[], + vars: binaryen.Type[], + ) { + const stmts: binaryen.ExpressionRef[] = []; + const i8Array = binaryenCAPI._BinaryenArrayNew( + module.ptr, + i8ArrayTypeInfo.heapTypeRef, + lengthRef, + module.i32.const(0), + ); + const arrayBufferRef = binaryenCAPI._BinaryenStructNew( + module.ptr, + arrayToPtr([i8Array, lengthRef]).ptr, + 2, + arrayBufferTypeInfo.heapTypeRef, + ); + stmts.push(module.local.set(startIdx, arrayBufferRef)); + vars.push(arrayBufferTypeInfo.typeRef); + calledParamValueRefs.push( + module.local.get(startIdx, arrayBufferTypeInfo.typeRef), + ); + startIdx++; + stmts.push(module.local.set(startIdx, module.i32.const(0))); + vars.push(binaryen.i32); + const loopIndexValue = module.local.get(startIdx, binaryen.i32); + const codesArray = binaryenCAPI._BinaryenStructGet( + module.ptr, + 0, + arrayBufferRef, + arrayBufferTypeInfo.typeRef, + false, + ); + /* Put elem in arraybuffer */ + const loopLabel = 'for_label'; + const loopCond = module.i32.lt_s(loopIndexValue, lengthRef); + const loopIncrementor = module.local.set( + startIdx, + module.i32.add(loopIndexValue, module.i32.const(1)), + ); + const loopBody: binaryen.ExpressionRef[] = []; + loopBody.push( + binaryenCAPI._BinaryenArraySet( + module.ptr, + codesArray, + loopIndexValue, + module.i32.load8_s( + 0, + 1, + module.i32.add(offsetValueRef, loopIndexValue), + ), + ), + ); + const flattenLoop: FlattenLoop = { + label: loopLabel, + condition: loopCond, + statements: module.block(null, loopBody), + incrementor: loopIncrementor, + }; + stmts.push( + module.loop( + loopLabel, + FunctionalFuncs.flattenLoopStatement( + module, + flattenLoop, + SemanticsKind.FOR, + ), + ), + ); + return module.block(null, stmts); + } + + export function generateConvertRule( + fromType: ValueType, + toType: ValueType, + ) { + if (fromType.kind === ValueTypeKind.OBJECT) { + const className = (fromType as ObjectType).meta.name; + if (className === BuiltinNames.ARRAYBUFFER) { + if (toType.kind === ValueTypeKind.INT) { + return NativeSignatureConversion.ARRAYBUFFER_TO_I32; + } + } + } else if (fromType.kind === ValueTypeKind.INT) { + if (toType.kind === ValueTypeKind.OBJECT) { + const className = (toType as ObjectType).meta.name; + if (className === BuiltinNames.ARRAYBUFFER) { + return NativeSignatureConversion.I32_TO_ARRAYBUFFER; + } + } else if (toType.kind === ValueTypeKind.INT) { + return NativeSignatureConversion.I32_TO_I32; + } + } + return NativeSignatureConversion.INVALID; + } + + export function parseNativeSignatureConversion( + fromTypes: ValueType[], + toTypes: ValueType[], + ) { + /* fromTypes is the wrapper functions' parameter types, toTypes is the real functions's parameter types */ + if (fromTypes.length !== toTypes.length) { + throw new Error( + `NativeSignature's parameter length must match real function's parameter length`, + ); + } + const convertRules: NativeSignatureConversion[] = []; + for (let i = 0; i < fromTypes.length; i++) { + convertRules.push(generateConvertRule(fromTypes[i], toTypes[i])); + } + return convertRules; + } + + export function parseNativeSignature( + module: binaryen.Module, + innerOpStmts: binaryen.ExpressionRef[], + fromTypes: ValueType[], + fromTypeRefs: binaryen.Type[], + toTypes: ValueType[], + skipEnvParamLen: number, + calledParamValueRefs: binaryen.ExpressionRef[], + vars: binaryen.Type[], + isImport: boolean, + ) { + const convertRules = FunctionalFuncs.parseNativeSignatureConversion( + fromTypes, + toTypes, + ); + let tmpVarIdx = isImport + ? fromTypes.length + skipEnvParamLen + : fromTypes.length; + for (let i = 0; i < convertRules.length; i++) { + const fromRef = module.local.get( + isImport ? i + skipEnvParamLen : i, + fromTypeRefs[i], + ); + const varsStartLen = vars.length; + switch (convertRules[i]) { + case NativeSignatureConversion.ARRAYBUFFER_TO_I32: { + innerOpStmts.push( + copyArrayBufferToLinearMemory( + module, + fromRef, + tmpVarIdx, + calledParamValueRefs, + vars, + ), + ); + break; + } + case NativeSignatureConversion.I32_TO_ARRAYBUFFER: { + assert(i + 1 < convertRules.length, `${i + 1} must exsit`); + const lengthRef = module.local.get( + isImport ? i + skipEnvParamLen + 1 : i + 1, + fromTypeRefs[i + 1], + ); + innerOpStmts.push( + copyLinearMemoryToArrayBuffer( + module, + fromRef, + lengthRef, + tmpVarIdx, + calledParamValueRefs, + vars, + ), + ); + break; + } + case NativeSignatureConversion.I32_TO_I32: { + calledParamValueRefs.push( + module.local.get(i + skipEnvParamLen, binaryen.i32), + ); + break; + } + case NativeSignatureConversion.INVALID: { + throw new Error( + 'nativeSignature conversion rule is invalid', + ); + } + default: { + throw new Error('not implemented yet'); + } + } + tmpVarIdx += vars.length - varsStartLen; + } + } } diff --git a/src/backend/binaryen/wasm_expr_gen.ts b/src/backend/binaryen/wasm_expr_gen.ts index a04c4c4c..2a4f51fd 100644 --- a/src/backend/binaryen/wasm_expr_gen.ts +++ b/src/backend/binaryen/wasm_expr_gen.ts @@ -2731,9 +2731,13 @@ export class WASMExpressionGen { if ( BuiltinNames.builtInObjectTypes.includes(typeMeta.name) ) { + const propertyIdx = this.getTruthIdx( + typeMeta, + typeMember, + ); return this.getBuiltinObjField( objRef, - value.index, + propertyIdx, this.wasmTypeGen.getWASMType(ownerType), ); } else { diff --git a/src/backend/binaryen/wasm_type_gen.ts b/src/backend/binaryen/wasm_type_gen.ts index 7b6910c5..8c4d06d5 100644 --- a/src/backend/binaryen/wasm_type_gen.ts +++ b/src/backend/binaryen/wasm_type_gen.ts @@ -53,7 +53,7 @@ import { needSpecialized } from '../../semantics/type_creator.js'; import { getConfig } from '../../../config/config_mgr.js'; export class WASMTypeGen { - typeMap: Map = new Map(); + private typeMap: Map = new Map(); /** it used for rec types, they share this._tb */ private _tb: binaryenCAPI.TypeBuilderRef = binaryenCAPI._TypeBuilderCreate(1); diff --git a/src/scope.ts b/src/scope.ts index 67c155f5..059f7178 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -15,7 +15,15 @@ import { TSContext, } from './type.js'; import { ParserContext } from './frontend.js'; -import { parentIsFunctionLike, isTypeGeneric } from './utils.js'; +import { + parentIsFunctionLike, + isTypeGeneric, + NativeSignature, + Import, + Export, + parseComment, + parseCommentBasedNode, +} from './utils.js'; import { Parameter, Variable } from './variable.js'; import { Statement } from './statement.js'; import { BuiltinNames } from '../lib/builtin/builtin_name.js'; @@ -690,6 +698,7 @@ export class FunctionScope extends ClosureEnvironment { /* ori func name iff func is declare */ oriFuncName: string | undefined = undefined; debugLocations: SourceMapLoc[] = []; + comments: (NativeSignature | Import | Export)[] = []; constructor(parent: Scope) { super(parent); @@ -857,6 +866,7 @@ export class ScopeScanner { ) { const parentScope = this.currentScope!; const functionScope = new FunctionScope(parentScope); + parseCommentBasedNode(node, functionScope); if (node.modifiers !== undefined) { for (const modifier of node.modifiers) { functionScope.addModifier(modifier); @@ -1213,6 +1223,7 @@ export class ScopeScanner { private _generateFuncScope(node: ts.FunctionLikeDeclaration) { const parentScope = this.currentScope!; const functionScope = new FunctionScope(parentScope); + parseCommentBasedNode(node, functionScope); if (node.modifiers !== undefined) { for (const modifier of node.modifiers) { functionScope.addModifier(modifier); diff --git a/src/semantics/index.ts b/src/semantics/index.ts index ca1084ed..e32b4303 100644 --- a/src/semantics/index.ts +++ b/src/semantics/index.ts @@ -16,6 +16,7 @@ import { ExternModule, ExternType, ExternTypeKind, + NativeSignature, } from './semantics_nodes.js'; import { Logger } from '../log.js'; import { ParserContext } from '../frontend.js'; @@ -29,7 +30,7 @@ import { ObjectType, EnumType, } from './value_types.js'; -import { PredefinedTypeId } from '../utils.js'; +import { PredefinedTypeId, isNativeSignatureComment } from '../utils.js'; import { GetPredefinedType } from './predefined_types.js'; import { flattenFunction } from './flatten.js'; import { BuildContext, SymbolKey, SymbolValue } from './builder_context.js'; @@ -294,6 +295,22 @@ function createFunctionDeclareNode( this_type, ); func.debugFilePath = f.debugFilePath; + for (const comment of f.comments) { + if (isNativeSignatureComment(comment)) { + const paramValueTypes: ValueType[] = []; + for (const paramType of comment.paramTypes) { + paramValueTypes.push(createType(context, paramType)); + } + const returnValueType = createType(context, comment.returnType); + const obj: NativeSignature = { + paramTypes: paramValueTypes, + returnType: returnValueType, + }; + func.comments.push(obj); + } else { + func.comments.push(comment); + } + } return func; } diff --git a/src/semantics/semantics_nodes.ts b/src/semantics/semantics_nodes.ts index fb6463ee..573cd94b 100644 --- a/src/semantics/semantics_nodes.ts +++ b/src/semantics/semantics_nodes.ts @@ -43,7 +43,7 @@ import { } from './value_types.js'; import { GetPredefinedType } from './predefined_types.js'; -import { PredefinedTypeId, SourceLocation } from '../utils.js'; +import { Export, Import, PredefinedTypeId, SourceLocation } from '../utils.js'; export enum SemanticsKind { EMPTY, @@ -231,12 +231,18 @@ export enum FunctionOwnKind { START = 64, } +export interface NativeSignature { + paramTypes: ValueType[]; + returnType: ValueType; +} + export class FunctionDeclareNode extends SemanticsNode { public flattenValue?: SemanticsValue; private _closureVars?: VarDeclareNode[]; public isInEnterScope = false; public importStartFuncNameList: string[] | undefined = undefined; public debugFilePath = ''; + public comments: (NativeSignature | Import | Export)[] = []; constructor( public name: string, public ownKind: FunctionOwnKind, diff --git a/src/type.ts b/src/type.ts index e071a330..26b5dd2a 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1281,9 +1281,11 @@ export class TypeResolver { this.typechecker = this.parserCtx.typeChecker; } /* Resolve wasm specific type */ - const maybeWasmType = TypeResolver.maybeBuiltinWasmType(node); - if (maybeWasmType) { - return maybeWasmType; + if (!ts.isFunctionLike(node)) { + const maybeWasmType = TypeResolver.maybeBuiltinWasmType(node); + if (maybeWasmType) { + return maybeWasmType; + } } const cached_type = this.nodeTypeCache.get(node); if (cached_type) { @@ -1834,13 +1836,20 @@ export class TypeResolver { }); /* parse return type */ - const returnType = - this.typechecker!.getReturnTypeOfSignature(signature); + const returnType = signature.getReturnType(); tsFunction.returnType = this.tsTypeToType(returnType); + /* builtin wasm types */ + const maybeWasmType = TypeResolver.maybeBuiltinWasmType( + signature.getDeclaration(), + ); + if (maybeWasmType) { + tsFunction.returnType = maybeWasmType; + } this.nodeTypeCache.set(decl, tsFunction); return tsFunction; } + private parseNestDeclare( node: | ts.FunctionLikeDeclaration diff --git a/src/utils.ts b/src/utils.ts index 259c5144..5cad28db 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,10 +30,13 @@ import { TSFunction, TSArray, TSUnion, + builtinTypes, + builtinWasmTypes, } from './type.js'; import { UnimplementError } from './error.js'; import { Statement } from './statement.js'; import { Variable, Parameter } from './variable.js'; +import { Logger } from './log.js'; export interface importGlobalInfo { internalName: string; @@ -61,6 +64,26 @@ export enum MatchKind { MisMatch, } +export enum CommentKind { + NativeSignature = 'NativeSignature', + Import = 'Import', + Export = 'Export', +} + +export interface NativeSignature { + paramTypes: Type[]; + returnType: Type; +} + +export interface Import { + moduleName: string; + funcName: string; +} + +export interface Export { + exportName: string; +} + export class Stack { private items: T[] = []; push(item: T) { @@ -753,3 +776,143 @@ export enum PredefinedTypeId { } export const DefaultTypeId = -1; export const CustomTypeId = PredefinedTypeId.CUSTOM_TYPE_BEGIN; + +export function getBuiltinType(typeStr: string): Type | undefined { + if (builtinTypes.has(typeStr)) { + return builtinTypes.get(typeStr); + } else if (builtinWasmTypes.has(typeStr)) { + return builtinWasmTypes.get(typeStr); + } else { + return undefined; + } +} + +export function isImportComment(obj: any): obj is Import { + return obj && 'moduleName' in obj; +} + +export function isNativeSignatureComment(obj: any): obj is NativeSignature { + return obj && 'paramTypes' in obj; +} + +export function isExportComment(obj: any): obj is Export { + return obj && 'exportName' in obj; +} + +export function parseComment(commentStr: string) { + commentStr = commentStr.replace(/\s/g, ''); + if (!commentStr.includes('Wasmnizer-ts')) { + return null; + } + const commentKindReg = commentStr.match(/@([^@]+)@/); + if (!commentKindReg) { + return null; + } + const commentKind = commentKindReg[1]; + switch (commentKind) { + case CommentKind.NativeSignature: { + const signatureStrReg = commentStr.match(/@([^@]+)$/); + if (!signatureStrReg) { + Logger.error('invalid signature in NativeSignature comment'); + return null; + } + const signatureStr = signatureStrReg[1]; + const signatureReg = signatureStr.match(/\(([^)]*)\)\s*=>\s*(\w+)/); + if (!signatureReg) { + Logger.error('invalid signature in NativeSignature comment'); + return null; + } + const parameterTypesArr = signatureReg[1].split(/\s*,\s*/); + const returnTypeStr = signatureReg[2]; + const paramTypes: Type[] = []; + for (const paramStr of parameterTypesArr) { + const builtinType = getBuiltinType(paramStr); + if (!builtinType) { + Logger.error( + 'unsupported signature type in NativeSignature comment', + ); + return null; + } + paramTypes.push(builtinType); + } + const builtinType = getBuiltinType(returnTypeStr); + if (!builtinType) { + Logger.error( + 'unsupported signature type in NativeSignature comment', + ); + return null; + } + const returnType = builtinType; + const obj: NativeSignature = { + paramTypes: paramTypes, + returnType: returnType, + }; + return obj; + } + case CommentKind.Import: { + const importInfoReg = commentStr.match( + /@Import@([a-zA-Z0-9_$]+),([a-zA-Z0-9_$]+$)/, + ); + if (!importInfoReg) { + Logger.error('invalid information in Import comment'); + return null; + } + const moduleName = importInfoReg[1]; + const funcName = importInfoReg[2]; + const obj: Import = { + moduleName: moduleName, + funcName: funcName, + }; + return obj; + } + case CommentKind.Export: { + const exportInfoReg = commentStr.match(/@Export@([a-zA-Z0-9_$]+$)/); + if (!exportInfoReg) { + Logger.error('invalid information in Export comment'); + return null; + } + const exportName = exportInfoReg[1]; + const obj: Export = { + exportName: exportName, + }; + return obj; + } + default: { + Logger.error(`unsupported comment kind ${commentKind}`); + return null; + } + } +} + +export function parseCommentBasedNode( + node: ts.FunctionLikeDeclaration, + functionScope: FunctionScope, +) { + const commentRanges = ts.getLeadingCommentRanges( + node.getSourceFile().getFullText(), + node.getFullStart(), + ); + if (commentRanges?.length) { + const commentStrings: string[] = commentRanges.map((r) => + node.getSourceFile().getFullText().slice(r.pos, r.end), + ); + for (const commentStr of commentStrings) { + const parseRes = parseComment(commentStr); + if (parseRes) { + const idx = functionScope.comments.findIndex((item) => { + return ( + (isExportComment(item) && isExportComment(parseRes)) || + (isImportComment(item) && isImportComment(parseRes)) || + (isNativeSignatureComment(item) && + isNativeSignatureComment(parseRes)) + ); + }); + if (idx !== -1) { + functionScope.comments[idx] = parseRes; + } else { + functionScope.comments.push(parseRes); + } + } + } + } +} diff --git a/tests/samples/arraybuffer_basic.ts b/tests/samples/arraybuffer_basic.ts index e2bc23aa..91633f8a 100644 --- a/tests/samples/arraybuffer_basic.ts +++ b/tests/samples/arraybuffer_basic.ts @@ -6,9 +6,8 @@ export function getArrayBufferLength() { const a = new ArrayBuffer(10); const a_sub = a.slice(0, 1); - /* TODO: wasmType not ready */ - // console.log(a.byteLength); - // console.log(a_sub.byteLength); + console.log(a.byteLength); + console.log(a_sub.byteLength); } export function arrayBufferIsView() { diff --git a/tests/samples/comments_not_entry.ts b/tests/samples/comments_not_entry.ts new file mode 100644 index 00000000..f56f306c --- /dev/null +++ b/tests/samples/comments_not_entry.ts @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +// Wasmnizer-ts: @Export@ nameBNotInEntry +export function nameANotInEntry() { + console.log('exportName is nameBNotInEntry'); +} diff --git a/tests/samples/comments_with_export.ts b/tests/samples/comments_with_export.ts new file mode 100644 index 00000000..496c8e3b --- /dev/null +++ b/tests/samples/comments_with_export.ts @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +import { nameANotInEntry } from "./comments_not_entry"; + +// Wasmnizer-ts: @Export@ nameB +export function nameA() { + console.log('exportName is nameB'); +} + +// Wasmnizer-ts: @Export@ nameD +// Wasmnizer-ts: @NativeSignature@ (i32, i32)=>void +export function nameC(arrayBuffer: ArrayBuffer, length: i32) { + console.log('exportName is nameD'); +} diff --git a/tests/samples/comments_with_import.ts b/tests/samples/comments_with_import.ts new file mode 100644 index 00000000..5e66c4ed --- /dev/null +++ b/tests/samples/comments_with_import.ts @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +// Wasmnizer-ts: @Export@ nameF +// Wasmnizer-ts: @NativeSignature@ (i32, i32)=>i32 +export declare function nameE(arrayBuffer: ArrayBuffer, length: i32): i32; + +// Wasmnizer-ts: @Import@ wamr, nameH +// Wasmnizer-ts: @NativeSignature@ (i32, i32)=>i32 +declare function nameG(buffer: ArrayBuffer, size: i32): void; + +export function callDeclare() { + const a = new ArrayBuffer(10); + nameG(a, 10); +} diff --git a/tests/samples/dataview_basic.ts b/tests/samples/dataview_basic.ts index 43c325c9..5092ff31 100644 --- a/tests/samples/dataview_basic.ts +++ b/tests/samples/dataview_basic.ts @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ -export function getdataViewProperty() { +export function getDataViewProperty() { const a = new ArrayBuffer(10); const d = new DataView(a, 1, 5); - /* TODO: wasmType not ready */ - // console.log(d.byteLength); - // console.log(d.byteOffset); + console.log(d.byteLength); + console.log(d.byteOffset); } export function newDataView() { @@ -19,11 +18,10 @@ export function newDataView() { const d2 = new DataView(a, 5); d2.setInt8(0, -5); console.log(d2.getInt8(0)); - /* TODO: wasmType not ready */ - // console.log(d1.byteLength); - // console.log(d1.byteOffset); - // console.log(d2.byteLength); - // console.log(d2.byteOffset); + console.log(d1.byteLength); + console.log(d1.byteOffset); + console.log(d2.byteLength); + console.log(d2.byteOffset); } export function dataViewI8() { diff --git a/tests/unit/comment.test.ts b/tests/unit/comment.test.ts new file mode 100644 index 00000000..eaa4bc14 --- /dev/null +++ b/tests/unit/comment.test.ts @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +import 'mocha'; +import { expect } from 'chai'; +import { + BlockNode, + FunctionDeclareNode, + FunctionOwnKind, + NativeSignature, +} from '../../src/semantics/semantics_nodes.js'; +import { FunctionType, WASM } from '../../src/semantics/value_types.js'; +import { builtinTypes } from '../../src/semantics/builtin.js'; +import { + Export, + Import, + NativeSignature as NativeSignatureFrontEnd, + getBuiltinType, + isExportComment, + isImportComment, + isNativeSignatureComment, + parseComment, +} from '../../src/utils.js'; +import { FunctionalFuncs } from '../../src/backend/binaryen/utils.js'; +import binaryen from 'binaryen'; +import { arrayBufferTypeInfo } from '../../src/backend/binaryen/glue/packType.js'; + +describe('testParseNativeSignature', function () { + it('ARRAYBUFFER_TO_I32', function () { + const func = new FunctionDeclareNode( + 'funcA', + FunctionOwnKind.DEFAULT, + new FunctionType(-1, WASM.I32, [ + builtinTypes.get('ArrayBuffer')!, + WASM.I32, + ]), + new BlockNode([]), + ); + const signatureComment: NativeSignature = { + paramTypes: [WASM.I32, WASM.I32], + returnType: WASM.I32, + }; + func.comments.push(signatureComment); + const module = new binaryen.Module(); + const innerOpStmts: binaryen.ExpressionRef[] = []; + const calledParamValueRefs: binaryen.ExpressionRef[] = []; + const vars: binaryen.ExpressionRef[] = []; + FunctionalFuncs.parseNativeSignature( + module, + innerOpStmts, + func.funcType.argumentsType, + [arrayBufferTypeInfo.typeRef, binaryen.i32], + signatureComment.paramTypes, + 2, + calledParamValueRefs, + vars, + true, + ); + + expect(calledParamValueRefs.length).eq(2); + expect(vars.length).eq(1); + expect(vars[0]).eq(binaryen.i32); + }); + it('I32_TO_ARRAYBUFFER', function () { + const func = new FunctionDeclareNode( + 'funcA', + FunctionOwnKind.DEFAULT, + new FunctionType(-1, WASM.I32, [ + builtinTypes.get('ArrayBuffer')!, + WASM.I32, + ]), + new BlockNode([]), + ); + const signatureComment: NativeSignature = { + paramTypes: [WASM.I32, WASM.I32], + returnType: WASM.I32, + }; + func.comments.push(signatureComment); + const module = new binaryen.Module(); + const innerOpStmts: binaryen.ExpressionRef[] = []; + const calledParamValueRefs: binaryen.ExpressionRef[] = []; + const vars: binaryen.ExpressionRef[] = []; + FunctionalFuncs.parseNativeSignature( + module, + innerOpStmts, + signatureComment.paramTypes, + [binaryen.i32, binaryen.i32], + func.funcType.argumentsType, + 2, + calledParamValueRefs, + vars, + false, + ); + + expect(calledParamValueRefs.length).eq(2); + expect(vars.length).eq(2); + expect(vars[0]).eq(arrayBufferTypeInfo.typeRef); + expect(vars[1]).eq(binaryen.i32); + }); +}); + +describe('testParseComment', function () { + it('parseNativeSignatureTrue', function () { + const commentStr = '// Wasmnizer-ts: @NativeSignature@ (i32, i32)=>i32'; + const res = parseComment(commentStr); + const isNativeSignature = isNativeSignatureComment(res); + const paramTypes = (res as NativeSignatureFrontEnd).paramTypes; + const returnType = (res as NativeSignatureFrontEnd).returnType; + + expect(isNativeSignature).eq(true); + expect(paramTypes.length).eq(2); + expect(paramTypes[0]).eq(getBuiltinType('i32')); + expect(paramTypes[1]).eq(getBuiltinType('i32')); + expect(returnType).eq(getBuiltinType('i32')); + + const commentStr2 = + '// Wasmnizer-ts: @NativeSignature@ (anyref, f64)=>number'; + const res2 = parseComment(commentStr2); + const isNativeSignature2 = isNativeSignatureComment(res2); + const paramTypes2 = (res2 as NativeSignatureFrontEnd).paramTypes; + const returnType2 = (res2 as NativeSignatureFrontEnd).returnType; + + expect(isNativeSignature2).eq(true); + expect(paramTypes2.length).eq(2); + expect(paramTypes2[0]).eq(getBuiltinType('anyref')); + expect(paramTypes2[1]).eq(getBuiltinType('f64')); + expect(returnType2).eq(getBuiltinType('number')); + }); + it('parseNativeSignatureTrueWithMultiTab', function () { + const commentStr = + '// Wasmnizer-ts: @NativeSignature@ (i32 , i32 )=> i32'; + const res = parseComment(commentStr); + const isNativeSignature = isNativeSignatureComment(res); + const paramTypes = (res as NativeSignatureFrontEnd).paramTypes; + const returnType = (res as NativeSignatureFrontEnd).returnType; + + expect(isNativeSignature).eq(true); + expect(paramTypes.length).eq(2); + expect(paramTypes[0]).eq(getBuiltinType('i32')); + expect(paramTypes[1]).eq(getBuiltinType('i32')); + expect(returnType).eq(getBuiltinType('i32')); + }); + it('parseNativeSignatureFalseWithWrongSpecificStr', function () { + const commentStr = '//Wasmnizer-js: @NativeSignature@ (i32, i32)=>i32'; + const res = parseComment(commentStr); + expect(res).eq(null); + + const commentStr2 = '//Wasmnizer-ts: @NativeSignature (i32, i32)=>i32'; + const res2 = parseComment(commentStr2); + expect(res2).eq(null); + + const commentStr3 = '//Wasmnizer-ts: @NativeSignature@ (i32, i32)=i32'; + const res3 = parseComment(commentStr3); + expect(res3).eq(null); + }); + it('parseNativeSignatureFalseWithInValidType', function () { + const commentStr = + '//Wasmnizer-ts: @NativeSignature@ (ArrayBuffer)=>i32'; + const res = parseComment(commentStr); + expect(res).eq(null); + + const commentStr2 = + '//Wasmnizer-ts: @NativeSignature (i32)=>ArrayBuffer'; + const res2 = parseComment(commentStr2); + expect(res2).eq(null); + }); + it('parseImportTrue', function () { + const commentStr = '// Wasmnizer-ts: @Import@ wamr, nameH'; + const res = parseComment(commentStr); + const isImport = isImportComment(res); + const moduleName = (res as Import).moduleName; + const funcName = (res as Import).funcName; + + expect(isImport).eq(true); + expect(moduleName).eq('wamr'); + expect(funcName).eq('nameH'); + }); + it('parseImportFalse', function () { + const commentStr = '// Wasmnizer-ts: @Impor@ wamr, nameH'; + const res = parseComment(commentStr); + expect(res).eq(null); + + const commentStr2 = '// Wasmnizer-ts: @Import@ wamr'; + const res2 = parseComment(commentStr2); + expect(res2).eq(null); + + const commentStr3 = '// Wasmnizer-ts: @Import@ wamr, n, q'; + const res3 = parseComment(commentStr3); + expect(res3).eq(null); + }); + it('parseExportTrue', function () { + const commentStr = '// Wasmnizer-ts: @Export@ nameD'; + const res = parseComment(commentStr); + const isExport = isExportComment(res); + const exportName = (res as Export).exportName; + + expect(isExport).eq(true); + expect(exportName).eq('nameD'); + }); + it('parseExportFalse', function () { + const commentStr = '// Wasmnizer-ts: @Export@ nameD, nameE'; + const res = parseComment(commentStr); + + expect(res).eq(null); + }); +}); diff --git a/tools/validate/wamr/validation.json b/tools/validate/wamr/validation.json index a726264a..e4f91c5e 100644 --- a/tools/validate/wamr/validation.json +++ b/tools/validate/wamr/validation.json @@ -1902,6 +1902,26 @@ } ] }, + { + "module": "comments_with_export", + "entries": [ + { + "name": "nameB", + "args": [], + "result": "exportName is nameB" + }, + { + "name": "nameBNotInEntry", + "args": [], + "result": "exportName is nameBNotInEntry" + }, + { + "name": "nameD", + "args": [1, 1], + "result": "exportName is nameD" + } + ] + }, { "module": "complexType_case1", "entries": [ @@ -1957,10 +1977,15 @@ { "module": "dataview_basic", "entries": [ + { + "name": "getDataViewProperty", + "args": [], + "result": "5\n1" + }, { "name": "newDataView", "args": [], - "result": "5\n-5" + "result": "5\n-5\n10\n0\n5\n5" }, { "name": "dataViewI8", @@ -3682,6 +3707,11 @@ { "module": "arraybuffer_basic", "entries": [ + { + "name": "getArrayBufferLength", + "args": [], + "result": "10\n1" + }, { "name": "arrayBufferIsView", "args": [],