diff --git a/doc/developer-guide/wasmType_usage.md b/doc/developer-guide/wasmType_usage.md index d16c3c4b..a84c40f1 100644 --- a/doc/developer-guide/wasmType_usage.md +++ b/doc/developer-guide/wasmType_usage.md @@ -1,12 +1,68 @@ # Use wasmType in typescript ## wasmType declaration -Now we support use wasmType directly in typescript, and the types must be explicitly specified: +Now we support use wasmType directly in typescript, these below types are supported: +### wasm basic type - `i32` - `i64` - `f32` - `f64` +- `anyref` +### wasm heap type +- `array` +- `struct` + ## wasmType usage -### For basic type +During usage, we must follow some rules. And the wasm basic type rules is differ from wasm heap type rules. +### wasm basic type +We can use the wasm basic type as ts type name directly. +### wasm heap type +We should set a special comment to indicate that a wasm heap type structure will be created. + +1. For `array`, we use `comment + array type alias` to represent the raw wasm array type. +```ts +// Wasmnizer-ts: @WASMArray@ +type arrayType1 = string[]; +---> will create a raw wasm array type: array + +// Wasmnizer-ts: @WASMArray@ +type arrayType2 = i32[]; +---> will create a raw wasm array type: array +``` +**Hint: `// Wasmnizer-ts: @WASMArray@ ` is necessary, and `` is optional. The latter shows that `if the array element is packed`, `if the array element is mutable`, `if the array is nullable`. The default value is `Not_Packed`, `Mutable` and `Nullable`.** + +2. For `struct`, we use `comment + tuple type alias` to represent the raw wasm struct type. +```ts +// Wasmnizer-ts: @WASMStruct@ <[Not_Packed, Not_Packed], [Mutable, Mutable], Nullable, NULL> +type structType1 = [arrayType1, i64]; +---> will create a raw wasm struct type: struct[array, i64] + +// Wasmnizer-ts: @WASMStruct@ +type structType2 = [i64, i32]; +---> will create a raw wasm struct type: struct[i64, i32] +``` +**Hint: `// Wasmnizer-ts: @WASMStruct@ ` is necessary, and `<[Not_Packed, ...], [Mutable, ...], Nullable, BaseTypeName>` is optional. The latter shows that `if the struct fields are packed`, `if the struct fields are mutable`, `if the struct is nullable`, `the struct's base type name`. The default value is `[Not_Packed, ...]`, `[Mutable, ...]`, `Nullable` and `NULL`.** + +The comments' optional attributes can be one of these enum value: +```ts +export enum PackedTypeKind { + Not_Packed = 'Not_Packed', + I8 = 'I8', + I16 = 'I16', +} + +export enum MutabilityKind { + Immutable = 'Immutable', + Mutable = 'Mutable', +} + +export enum NullabilityKind { + NonNullable = 'NonNullable', + Nullable = 'Nullable', +} +``` + +## Example +### Used as basic type If we define the wasmtype for variables, and the right value is LiteralValue or variables with the same wasmtype, the **no cast** will be generated. ```ts const a: i32 = 100; @@ -32,6 +88,27 @@ const a: f64 = 100; (f64.const 100) ``` +```ts +// Wasmnizer-ts: @WASMArray@ +type arrayType2 = i32[]; +const a: arrayType2 = [100]; +--> +(array.new_fixed $array0 1 + (i32.const 100) +) +``` + +```ts +// Wasmnizer-ts: @WASMStruct@ +type structType2 = [i64, i32]; +const a: structType2 = [100, 200] +---> +(struct.new $45 + (i64.const 100) + (i32.const 200) +) +``` + If we don't define the wasmtype explicitly, then the variable will be regard as `number` type, **one cast** will be occurs. ```ts const a = 100 as i32; @@ -42,12 +119,21 @@ const a = 100 as i32; ``` -### For array type +### Used as array element type The array type should be explicitly specified too. ```ts const a: i32[] = [1, 2]; --> a will be regarded as i32[], the elements in right value are both i32. +since we use struct to represent ts array, so the wasm structure is struct[array, i32]. +``` +```ts +const x: arrayType2 = [100]; +const y: arrayType2 = [200]; +const a: arrayType2[] = [x, y]; +--> +a will be regarded as arrayType2[], the elements in right value are both arrayType2. +since we use struct to represent ts array, so the wasm structure is struct[array>, i32]. ``` If array's type is not explicitly specified, then left value is regarded as number[], compilation error will occur. ```ts @@ -58,7 +144,7 @@ let a = [a1, a2]; a will be regarded as number[], compile will fail. ``` -### For class type +### Used as class property type Each property's wasm type should be explicitly specified. ```ts class A { @@ -66,11 +152,12 @@ class A { b: i64 = 2; c: f32 = 3; d: f64 = 4; + e: arrayType2 = [5]; } --> -The properties type are i32, i64, f32, f64 type. +The properties type are i32, i64, f32, f64, array type. ``` -If property's type is not explicitly specified, they will be regarded as number type, and **one cast** will occur. +If property's type is not explicitly specified, they will be regarded as original ts type, and **one cast** will occur. ```ts class A { a = 1 as i32; @@ -81,8 +168,16 @@ class A { --> The properties type are both number type, and a, b, c all will be cast to f64. ``` +Wasm heap type can not be used as casted target since the ts original type `number[]` can not be casted to `WASMArrayType`: +```ts +class A { + e = [5] as arrayType2 +} +--> +Will cause compilation error since `cannot make cast value from "Array(-1)" to "WASM_ARRAY(58)"` +``` -### For interface type +### Used as interface property type Each property's wasm type should be explicitly specified. ```ts interface I { @@ -90,22 +185,25 @@ interface I { b: i64; c: f32; d: f64; + e: arrayType2; } --> -The properties type are i32, i64, f32, f64 type. +The properties type are i32, i64, f32, f64, array type. ``` -### For object literal type +### Used as object literal property type Since object literal's properties' type can not be defined, we only provide its value, so we judge properties' type by its real value type. ```ts +const x: arrayType2 = [5]; const obj = { a: 1 as i32, b: 2 as i64, c: 3 as f32, d: 4 as f64, + e: x as arrayType2, } --> -The properties type are i32, i64, f32, f64 type. +The properties type are i32, i64, f32, f64, array type. ``` So, if we assign the obj's type to an interface type which has wasmtype, then we should ensure that the properties' value type should be wasmtype too. @@ -115,12 +213,15 @@ interface I { b: i64; c: f32; d: f64; + e: arrayType2; } +const x: arrayType2 = [5]; const obj: I = { a: 1 as i32, b: 2 as i64, c: 3 as f32, d: 4 as f64, + e: x as arrayType2, } ---> compile success @@ -131,18 +232,20 @@ interface I { b: i64; c: f32; d: f64; + e: arrayType2; } const obj: I = { a: 1, b: 2, c: 3, d: 4, + e: [5], } ---> compile fail ``` -### For funtion type +### Used as funtion param type & return type The parameter's type and return type should be explicitly specified when using wasmtype. ```ts function test(): i32 { @@ -165,7 +268,16 @@ One cast will occur, the return type is number. ) ``` -### binary operations +```ts +function test(): arrayType2 { + const x: arrayType2 = [100]; + return x; +} +--> +The return type is array. +``` + +### type casting in binary operations If two operators with wasm type operate binary operations, they will cast to the larger type, and operate. ```ts const a: i32 = 100; diff --git a/lib/builtin/builtin_name.ts b/lib/builtin/builtin_name.ts index 4892c9ea..651f3018 100644 --- a/lib/builtin/builtin_name.ts +++ b/lib/builtin/builtin_name.ts @@ -59,6 +59,7 @@ export namespace BuiltinNames { export const findPropertyFlagAndIndex = 'find_property_flag_and_index'; export const findPropertyType = 'find_property_type'; export const getInfcProperty = 'get_infc_property'; + export const getTupleField = 'get_tuple_field'; // builtin globals export const builtinTypeManglePrefix = 'lib/builtin/lib.type.d'; diff --git a/src/backend/binaryen/glue/packType.ts b/src/backend/binaryen/glue/packType.ts index 2227d26c..1c456120 100644 --- a/src/backend/binaryen/glue/packType.ts +++ b/src/backend/binaryen/glue/packType.ts @@ -18,12 +18,14 @@ import { arrayBufferType, dataViewType, numberArrayStructType, + i32ArrayType, } from './transform.js'; import { typeInfo } from './utils.js'; export const i8ArrayTypeInfo: typeInfo = i8ArrayType; export const stringTypeInfo: typeInfo = stringType; export const numberArrayTypeInfo = numberArrayType; +export const i32ArrayTypeInfo = i32ArrayType; export const stringArrayTypeInfo = stringArrayType; export const stringArrayStructTypeInfo = stringArrayStructType; export const stringrefArrayTypeInfo = stringrefArrayType; diff --git a/src/backend/binaryen/glue/transform.ts b/src/backend/binaryen/glue/transform.ts index f2da321d..2ed49cf9 100644 --- a/src/backend/binaryen/glue/transform.ts +++ b/src/backend/binaryen/glue/transform.ts @@ -242,6 +242,8 @@ export function initStructType( export const i8ArrayType = genarateI8ArrayTypeInfo(); /* array(f64) */ export const numberArrayType = genarateNumberArrayTypeInfo(); +/* array(i32) */ +export const i32ArrayType = genarateI32ArrayTypeInfo(); /* array(stringref) */ export const stringrefArrayType = genarateStringrefArrayTypeInfo(false); /* array(i32) */ @@ -355,6 +357,19 @@ function genarateNumberArrayTypeInfo(): typeInfo { return numberArrayTypeInfo; } +// generate i32 array type +function genarateI32ArrayTypeInfo(): typeInfo { + const i32ArrayTypeInfo = initArrayType( + binaryen.i32, + Packed.Not, + true, + true, + -1, + binaryenCAPI._TypeBuilderCreate(1), + ); + return i32ArrayTypeInfo; +} + // generate string array type function genarateStringArrayTypeInfo(struct_wrap: boolean): typeInfo { const stringTypeInfo = stringType; diff --git a/src/backend/binaryen/lib/init_builtin_api.ts b/src/backend/binaryen/lib/init_builtin_api.ts index 285082d2..1b50f7c4 100644 --- a/src/backend/binaryen/lib/init_builtin_api.ts +++ b/src/backend/binaryen/lib/init_builtin_api.ts @@ -38,6 +38,7 @@ import { dataViewTypeInfo, numberArrayStructTypeInfo, numberArrayTypeInfo, + i32ArrayTypeInfo, } from '../glue/packType.js'; import { array_get_data, array_get_length_i32 } from './array_utils.js'; import { SemanticsKind } from '../../../semantics/semantics_nodes.js'; @@ -3424,12 +3425,11 @@ function getPropertyIfTypeIdMismatch(module: binaryen.Module) { const infcPropTypeId_Idx = 1; const objPropTypeId_Idx = 2; const objRef_Idx = 3; - const tagRef_Idx = 4; /* locals */ - const flag_Idx = 5; - const index_Idx = 6; - const anyTypedRes_Idx = 7; + const flag_Idx = 4; + const index_Idx = 5; + const anyTypedRes_Idx = 6; const stmts = [ module.local.set( @@ -3448,6 +3448,13 @@ function getPropertyIfTypeIdMismatch(module: binaryen.Module) { ), ]; + const objPropTypeIdRefValue = module.local.get( + objPropTypeId_Idx, + binaryen.i32, + ); + const objRefValue = module.local.get(objRef_Idx, binaryen.anyref); + const i32IdxRefValue = module.local.get(index_Idx, binaryen.i32); + const ifPropertyUnExist = FunctionalFuncs.isPropertyUnExist( module, module.local.get(flagAndIndex_Idx, binaryen.i32), @@ -3462,123 +3469,17 @@ function getPropertyIfTypeIdMismatch(module: binaryen.Module) { module.local.get(infcPropTypeId_Idx, binaryen.i32), module.i32.const(PredefinedTypeId.ANY), ); - const infcPropIsAnyBranches: binaryen.ExpressionRef[] = new Array(4); - infcPropIsAnyBranches[0] = module.br( - 'case_obj_prop_type_is_number', - FunctionalFuncs.isPropTypeIdEqual( - module, - module.local.get(objPropTypeId_Idx, binaryen.i32), - module.i32.const(PredefinedTypeId.NUMBER), - ), - ); - infcPropIsAnyBranches[1] = module.br( - 'case_obj_prop_type_is_boolean', - FunctionalFuncs.isPropTypeIdEqual( - module, - module.local.get(objPropTypeId_Idx, binaryen.i32), - module.i32.const(PredefinedTypeId.BOOLEAN), - ), - ); - infcPropIsAnyBranches[2] = module.br( - 'case_obj_prop_type_is_string', - FunctionalFuncs.isPropTypeIdEqual( - module, - module.local.get(objPropTypeId_Idx, binaryen.i32), - module.i32.const(PredefinedTypeId.STRING), - ), - ); - infcPropIsAnyBranches[3] = module.br('obj_prop_type_default'); - - let infcPropIsAnyBlock = module.block( - 'case_obj_prop_type_is_number', - infcPropIsAnyBranches, - ); - infcPropIsAnyBlock = module.block( - 'case_obj_prop_type_is_boolean', - [infcPropIsAnyBlock].concat( - module.local.set( - anyTypedRes_Idx, - FunctionalFuncs.generateDynNumber( - module, - module.call( - structdyn.StructDyn.struct_get_indirect_f64, - [ - module.local.get(objRef_Idx, binaryen.anyref), - module.local.get(index_Idx, binaryen.i32), - ], - binaryen.f64, - ), - ), - ), - module.br('obj_prop_type_break'), - ), - ); - infcPropIsAnyBlock = module.block( - 'case_obj_prop_type_is_string', - [infcPropIsAnyBlock].concat( - module.local.set( - anyTypedRes_Idx, - FunctionalFuncs.generateDynBoolean( - module, - module.call( - structdyn.StructDyn.struct_get_indirect_i32, - [ - module.local.get(objRef_Idx, binaryen.anyref), - module.local.get(index_Idx, binaryen.i32), - ], - binaryen.i32, - ), - ), - ), - module.br('obj_prop_type_break'), - ), - ); - infcPropIsAnyBlock = module.block( - 'obj_prop_type_default', - [infcPropIsAnyBlock].concat( - module.local.set( - anyTypedRes_Idx, - FunctionalFuncs.generateDynString( - module, - module.call( - structdyn.StructDyn.struct_get_indirect_anyref, - [ - module.local.get(objRef_Idx, binaryen.anyref), - module.local.get(index_Idx, binaryen.i32), - ], - binaryen.anyref, - ), - ), - ), - module.br('obj_prop_type_break'), - ), - ); - infcPropIsAnyBlock = module.block( - 'obj_prop_type_break', - [infcPropIsAnyBlock].concat( - module.local.set( - anyTypedRes_Idx, - FunctionalFuncs.generateDynExtref( - module, - module.call( - structdyn.StructDyn.struct_get_indirect_anyref, - [ - module.local.get(objRef_Idx, binaryen.anyref), - module.local.get(index_Idx, binaryen.i32), - ], - binaryen.anyref, - ), - module.local.get(tagRef_Idx, binaryen.i32), - ), - ), - module.br('obj_prop_type_break'), - ), + const ifInfcPropIsAnyTrue = generateSwitchBlock( + module, + objPropTypeIdRefValue, + anyTypedRes_Idx, + objRefValue, + i32IdxRefValue, ); - const ifInfcPropIsAnyTrue = infcPropIsAnyBlock; const ifObjPropIsAny = FunctionalFuncs.isPropTypeIdEqual( module, - module.local.get(objPropTypeId_Idx, binaryen.i32), + objPropTypeIdRefValue, module.i32.const(PredefinedTypeId.ANY), ); @@ -3591,10 +3492,7 @@ function getPropertyIfTypeIdMismatch(module: binaryen.Module) { anyTypedRes_Idx, module.call( structdyn.StructDyn.struct_get_indirect_anyref, - [ - module.local.get(objRef_Idx, binaryen.anyref), - module.local.get(index_Idx, binaryen.i32), - ], + [objRefValue, i32IdxRefValue], binaryen.anyref, ), ), @@ -4982,6 +4880,296 @@ function string_fromCharCode(module: binaryen.Module) { return module.block(null, stmts); } +function generateSwitchBlock( + module: binaryen.Module, + typeIdRefValue: binaryen.ExpressionRef, + anyTypedRes_Idx: number, + ownerRefValue: binaryen.ExpressionRef, + idxI32RefValue: binaryen.ExpressionRef, +) { + const branches: binaryen.ExpressionRef[] = new Array(8); + branches[0] = module.br( + 'case_field_type_is_number', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.NUMBER), + ), + ); + branches[1] = module.br( + 'case_field_type_is_i32', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.INT), + ), + ); + branches[2] = module.br( + 'case_field_type_is_i64', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.WASM_I64), + ), + ); + branches[3] = module.br( + 'case_field_type_is_f32', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.WASM_F32), + ), + ); + branches[4] = module.br( + 'case_field_type_is_boolean', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.BOOLEAN), + ), + ); + branches[5] = module.br( + 'case_field_type_is_string', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.STRING), + ), + ); + branches[6] = module.br( + 'case_field_type_is_any', + FunctionalFuncs.isPropTypeIdEqual( + module, + typeIdRefValue, + module.i32.const(PredefinedTypeId.ANY), + ), + ); + branches[7] = module.br('field_type_default'); + + let switchBlock = module.block('case_field_type_is_number', branches); + switchBlock = module.block( + 'case_field_type_is_i32', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynNumber( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_f64, + [ownerRefValue, idxI32RefValue], + binaryen.f64, + ), + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'case_field_type_is_i64', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynNumber( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_i32, + [ownerRefValue, idxI32RefValue], + binaryen.i32, + ), + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'case_field_type_is_f32', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynNumber( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_i64, + [ownerRefValue, idxI32RefValue], + binaryen.i64, + ), + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'case_field_type_is_boolean', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynNumber( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_f32, + [ownerRefValue, idxI32RefValue], + binaryen.f32, + ), + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'case_field_type_is_string', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynBoolean( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_i32, + [ownerRefValue, idxI32RefValue], + binaryen.i32, + ), + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'case_field_type_is_any', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynString( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_anyref, + [ownerRefValue, idxI32RefValue], + binaryen.anyref, + ), + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'field_type_default', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + module.call( + structdyn.StructDyn.struct_get_indirect_anyref, + [ownerRefValue, idxI32RefValue], + binaryen.anyref, + ), + ), + module.br('field_type_break'), + ), + ); + switchBlock = module.block( + 'field_type_break', + [switchBlock].concat( + module.local.set( + anyTypedRes_Idx, + FunctionalFuncs.generateDynExtref( + module, + module.call( + structdyn.StructDyn.struct_get_indirect_anyref, + [ownerRefValue, idxI32RefValue], + binaryen.anyref, + ), + FunctionalFuncs.getExtTagRefByTypeIdRef( + module, + typeIdRefValue, + ), + ), + ), + module.br('field_type_break'), + ), + ); + return switchBlock; +} + +function WASMStruct_get_field(module: binaryen.Module) { + /* params */ + const typeIdArray_idx = 0; + const idxI32Ref_idx = 1; + const ownerRef_idx = 2; + /* vars */ + const arrLen_i32_idx = 3; + const loopIdx_i32_idx = 4; + const typeIdRef_idx = 5; + const anyTypedRes_Idx = 6; + + const stmts: binaryen.ExpressionRef[] = []; + stmts.push(module.local.set(loopIdx_i32_idx, module.i32.const(0))); + const typeIdArrayValue = module.local.get( + typeIdArray_idx, + i32ArrayTypeInfo.typeRef, + ); + const loopIndexValue = module.local.get(loopIdx_i32_idx, binaryen.i32); + const idxI32RefValue = module.local.get(idxI32Ref_idx, binaryen.i32); + const typeIdRefValue = module.local.get(typeIdRef_idx, binaryen.i32); + const ownerRefValue = module.local.get(ownerRef_idx, binaryen.anyref); + + const arrLen = binaryenCAPI._BinaryenArrayLen(module.ptr, typeIdArrayValue); + stmts.push(module.local.set(arrLen_i32_idx, arrLen)); + /* get the field typeId through for loop */ + const loopLabel = 'for_label'; + const loopCond = module.i32.lt_s( + loopIndexValue, + module.local.get(arrLen_i32_idx, binaryen.i32), + ); + const loopIncrementor = module.local.set( + loopIdx_i32_idx, + module.i32.add(loopIndexValue, module.i32.const(1)), + ); + const loopBody: binaryen.ExpressionRef[] = []; + loopBody.push( + module.if( + module.i32.eq(loopIndexValue, idxI32RefValue), + module.block(null, [ + module.local.set( + typeIdRef_idx, + binaryenCAPI._BinaryenArrayGet( + module.ptr, + typeIdArrayValue, + idxI32RefValue, + i32ArrayTypeInfo.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, + ), + ), + ); + const switchBlock = generateSwitchBlock( + module, + typeIdRefValue, + anyTypedRes_Idx, + ownerRefValue, + idxI32RefValue, + ); + stmts.push(switchBlock); + stmts.push( + module.return(module.local.get(anyTypedRes_Idx, binaryen.anyref)), + ); + + return module.block(null, stmts); +} + export function callBuiltInAPIs(module: binaryen.Module) { /** Math.sqrt */ module.addFunction( @@ -5113,7 +5301,6 @@ export function callBuiltInAPIs(module: binaryen.Module) { binaryen.i32, binaryen.i32, binaryen.anyref, - binaryen.i32, ]), binaryen.anyref, [binaryen.i32, binaryen.i32, binaryen.anyref], @@ -6214,6 +6401,17 @@ export function callBuiltInAPIs(module: binaryen.Module) { [binaryen.i32, binaryen.i32], string_fromCharCode(module), ); + module.addFunction( + BuiltinNames.getTupleField, + binaryen.createType([ + i32ArrayTypeInfo.typeRef, + binaryen.i32, + binaryen.anyref, + ]), + binaryen.anyref, + [binaryen.i32, binaryen.i32, binaryen.i32, binaryen.anyref], + WASMStruct_get_field(module), + ); } function addArrayMethod( diff --git a/src/backend/binaryen/utils.ts b/src/backend/binaryen/utils.ts index 877a5776..b8264121 100644 --- a/src/backend/binaryen/utils.ts +++ b/src/backend/binaryen/utils.ts @@ -430,7 +430,7 @@ export namespace FunctionalFuncs { ) { return module.call( dyntype.dyntype_new_number, - [getDynContextRef(module), dynValue], + [getDynContextRef(module), convertTypeToF64(module, dynValue)], dyntype.dyn_value_t, ); } @@ -2206,6 +2206,11 @@ export namespace FunctionalFuncs { ValueTypeKind.NUMBER, ), ); + } else if (arrayValue.type.kind === ValueTypeKind.WASM_ARRAY) { + arrLenI32Ref = binaryenCAPI._BinaryenArrayLen( + module.ptr, + arrStructRef, + ); } if (returnI32) { return arrLenI32Ref!; @@ -2247,25 +2252,59 @@ export namespace FunctionalFuncs { return strLenF64; } - export function getArrayElemByIdx( + export function setArrayElemByIdx( module: binaryen.Module, - elemTypeRef: binaryen.Type, ownerRef: binaryen.ExpressionRef, ownerHeapTypeRef: binaryenCAPI.HeapTypeRef, idxRef: binaryen.ExpressionRef, + targetValueRef: binaryen.ExpressionRef, + isRawArray = false, ) { - const arrayOriRef = binaryenCAPI._BinaryenStructGet( + let arrayOriRef: binaryen.ExpressionRef; + if (isRawArray) { + arrayOriRef = ownerRef; + } else { + arrayOriRef = binaryenCAPI._BinaryenStructGet( + module.ptr, + 0, + ownerRef, + ownerHeapTypeRef, + false, + ); + } + return binaryenCAPI._BinaryenArraySet( module.ptr, - 0, - ownerRef, - ownerHeapTypeRef, - false, + arrayOriRef, + idxRef, + targetValueRef, ); + } + + export function getArrayElemByIdx( + module: binaryen.Module, + ownerTypeRef: binaryen.Type, + ownerRef: binaryen.ExpressionRef, + ownerHeapTypeRef: binaryenCAPI.HeapTypeRef, + idxRef: binaryen.ExpressionRef, + isRawArray = false, + ) { + let arrayOriRef: binaryen.ExpressionRef; + if (isRawArray) { + arrayOriRef = ownerRef; + } else { + arrayOriRef = binaryenCAPI._BinaryenStructGet( + module.ptr, + 0, + ownerRef, + ownerHeapTypeRef, + false, + ); + } return binaryenCAPI._BinaryenArrayGet( module.ptr, arrayOriRef, idxRef, - elemTypeRef, + ownerTypeRef, false, ); } @@ -2414,6 +2453,12 @@ export namespace FunctionalFuncs { return PredefinedTypeId.WASM_I64; case ValueTypeKind.WASM_F32: return PredefinedTypeId.WASM_F32; + case ValueTypeKind.TUPLE: + return PredefinedTypeId.TUPLE; + case ValueTypeKind.WASM_ARRAY: + return PredefinedTypeId.WASM_ARRAY; + case ValueTypeKind.WASM_STRUCT: + return PredefinedTypeId.WASM_STRUCT; default: throw new UnimplementError( `encounter type not assigned type id, type kind is ${type.kind}`, diff --git a/src/backend/binaryen/wasm_expr_gen.ts b/src/backend/binaryen/wasm_expr_gen.ts index 3bf8da50..e8939524 100644 --- a/src/backend/binaryen/wasm_expr_gen.ts +++ b/src/backend/binaryen/wasm_expr_gen.ts @@ -87,11 +87,14 @@ import { ObjectTypeFlag, Primitive, PrimitiveType, + TupleType, TypeParameterType, UnionType, ValueType, ValueTypeKind, ValueTypeWithArguments, + WASMArrayType, + WASMStructType, } from '../../semantics/value_types.js'; import { UnimplementError } from '../../error.js'; import { @@ -109,11 +112,11 @@ import { NewConstructorObjectValue } from '../../semantics/value.js'; import { BuiltinNames } from '../../../lib/builtin/builtin_name.js'; import { dyntype, structdyn } from './lib/dyntype/utils.js'; import { - anyArrayTypeInfo, stringArrayStructTypeInfo, stringrefArrayStructTypeInfo, stringArrayTypeInfo, stringrefArrayTypeInfo, + i32ArrayTypeInfo, } from './glue/packType.js'; import { getBuiltInFuncName } from '../../utils.js'; import { stringTypeInfo } from './glue/packType.js'; @@ -217,10 +220,16 @@ export class WASMExpressionGen { case SemanticsValueKind.ARRAY_INDEX_GET: case SemanticsValueKind.OBJECT_KEY_GET: case SemanticsValueKind.STRING_INDEX_GET: + case SemanticsValueKind.TUPLE_INDEX_GET: + case SemanticsValueKind.WASMARRAY_INDEX_GET: + case SemanticsValueKind.WASMSTRUCT_INDEX_GET: return this.wasmElemGet(value); case SemanticsValueKind.ARRAY_INDEX_SET: case SemanticsValueKind.OBJECT_KEY_SET: case SemanticsValueKind.STRING_INDEX_SET: + case SemanticsValueKind.TUPLE_INDEX_SET: + case SemanticsValueKind.WASMARRAY_INDEX_SET: + case SemanticsValueKind.WASMSTRUCT_INDEX_SET: return this.wasmElemSet(value); case SemanticsValueKind.BLOCK: return this.wasmBlockValue(value); @@ -1536,8 +1545,15 @@ export class WASMExpressionGen { ); } } + case ValueTypeKind.WASM_ARRAY: { + throw new UnimplementError( + `unimplement wasmDynamicCall when type is WASM_ARRAY`, + ); + } default: - throw Error(`unimplement wasmDynamicCall in : ${value}`); + throw new UnimplementError( + `unimplement wasmDynamicCall in : ${value}`, + ); } } @@ -3033,16 +3049,7 @@ export class WASMExpressionGen { BuiltinNames.builtinModuleName, BuiltinNames.getPropertyIfTypeIdMismatch, ), - [ - flagAndIndexRef, - infcPropTypeIdRef, - propTypeIdRef, - objRef, - FunctionalFuncs.getExtTagRefByTypeIdRef( - this.module, - propTypeIdRef, - ), - ], + [flagAndIndexRef, infcPropTypeIdRef, propTypeIdRef, objRef], binaryen.anyref, ); ifPropTypeIdEqualFalse = FunctionalFuncs.unboxAny( @@ -3663,12 +3670,14 @@ export class WASMExpressionGen { ); } } + case ValueTypeKind.WASM_ARRAY: case ValueTypeKind.ARRAY: { if (propName === 'length') { const ownValueRef = this.wasmExprGen(owner); return FunctionalFuncs.getArrayRefLen( this.module, ownValueRef, + owner, ); } throw Error(`unhandle Array field get: ${propName}`); @@ -3683,6 +3692,17 @@ export class WASMExpressionGen { } throw Error(`unhandle String field get: ${propName}`); } + case ValueTypeKind.WASM_STRUCT: + case ValueTypeKind.TUPLE: { + if (propName === 'length') { + const fields = + owner.type instanceof TupleType + ? owner.type.elements + : (owner.type).tupleType.elements; + return this.module.f64.const(fields.length); + } + throw Error(`unhandle Array field get: ${propName}`); + } default: throw Error(`wasmDynamicGet: ${value}`); } @@ -3729,18 +3749,45 @@ export class WASMExpressionGen { } private wasmNewLiteralArray(value: NewLiteralArrayValue) { - return this.wasmElemsToArr(value.initValues, value.type as ArrayType); + switch (value.type.kind) { + case ValueTypeKind.ARRAY: + case ValueTypeKind.WASM_ARRAY: { + return this.wasmElemsToArr(value.initValues, value.type); + } + case ValueTypeKind.TUPLE: + case ValueTypeKind.WASM_STRUCT: { + const fieldRefs: binaryen.ExpressionRef[] = []; + for (const field of value.initValues) { + fieldRefs.push(this.wasmExprGen(field)); + } + const heapTypeRef = this.wasmTypeGen.getWASMHeapType( + value.type, + ); + return binaryenCAPI._BinaryenStructNew( + this.module.ptr, + arrayToPtr(fieldRefs).ptr, + value.initValues.length, + heapTypeRef, + ); + } + default: { + throw new UnimplementError( + `unimplement type kind ${value.type.kind} in wasmNewLiteralArray`, + ); + } + } } private wasmNewArray(value: NewArrayValue | NewArrayLenValue) { let arrayRef: binaryen.ExpressionRef; let arraySizeRef: binaryen.ExpressionRef; - const arrayHeapType = this.wasmTypeGen.getWASMArrayOriHeapType( - value.type, - ); - const arrayStructHeapType = this.wasmTypeGen.getWASMHeapType( - value.type, - ); + const arrayType: ArrayType = + value.type instanceof ArrayType + ? value.type + : (value.type).arrayType; + const arrayHeapType = + this.wasmTypeGen.getWASMArrayOriHeapType(arrayType); + const arrayStructHeapType = this.wasmTypeGen.getWASMHeapType(arrayType); if (value instanceof NewArrayValue) { const arrayLen = value.parameters.length; @@ -3757,9 +3804,7 @@ export class WASMExpressionGen { ); arraySizeRef = this.module.i32.const(arrayLen); } else if (value instanceof NewArrayLenValue) { - const arrayInit = this.getArrayInitFromArrayType( - value.type, - ); + const arrayInit = this.getArrayInitFromArrayType(arrayType); arraySizeRef = FunctionalFuncs.convertTypeToI32( this.module, this.wasmExprGen(value.len), @@ -3779,7 +3824,10 @@ export class WASMExpressionGen { 2, arrayStructHeapType, ); - return arrayStructRef; + + const res = + value.type instanceof ArrayType ? arrayStructRef : arrayRef!; + return res; } private elemOp(value: ElementGetValue | ElementSetValue) { @@ -3833,23 +3881,23 @@ export class WASMExpressionGen { const owner = value.owner; const ownerType = owner.type; switch (ownerType.kind) { - case ValueTypeKind.ARRAY: { + case ValueTypeKind.ARRAY: + case ValueTypeKind.WASM_ARRAY: { const ownerRef = this.wasmExprGen(owner); const idxI32Ref = FunctionalFuncs.convertTypeToI32( this.module, this.wasmExprGen(value.index), ); - const elemTypeRef = this.wasmTypeGen.getWASMType( - (ownerType as ArrayType).element, - ); + const ownerTypeRef = this.wasmTypeGen.getWASMType(ownerType); const ownerHeapTypeRef = this.wasmTypeGen.getWASMHeapType(ownerType); return FunctionalFuncs.getArrayElemByIdx( this.module, - elemTypeRef, + ownerTypeRef, ownerRef, ownerHeapTypeRef, idxI32Ref, + ownerType.kind === ValueTypeKind.ARRAY ? false : true, ); } /* workaround: sometimes semantic tree will treat array as any @@ -3919,6 +3967,82 @@ export class WASMExpressionGen { case ValueTypeKind.OBJECT: { return this.elemOp(value); } + case ValueTypeKind.TUPLE: + case ValueTypeKind.WASM_STRUCT: { + const ownerRef = this.wasmExprGen(owner); + const ownerHeapTypeRef = + this.wasmTypeGen.getWASMHeapType(ownerType); + const fields = + ownerType instanceof TupleType + ? ownerType.elements + : (ownerType as WASMStructType).tupleType.elements; + if (value.index instanceof LiteralValue) { + /* If we can get constant, then invoke BinaryenStructGet */ + const idx = value.index.value as number; + return binaryenCAPI._BinaryenStructGet( + this.module.ptr, + idx, + ownerRef, + ownerHeapTypeRef, + false, + ); + } else { + if (ownerType instanceof WASMStructType) { + throw new Error( + `index must be a constant in WASMStruct getting`, + ); + } + /* If we can not get constant as index, then invoke struct_get_indirect */ + const idxI32Ref = FunctionalFuncs.convertTypeToI32( + this.module, + this.wasmExprGen(value.index), + ); + + const wasmRawArrayLocal = + this.wasmCompiler.currentFuncCtx!.insertTmpVar( + i32ArrayTypeInfo.typeRef, + ); + const newWasmRawArrayOp = this.module.local.set( + wasmRawArrayLocal.index, + binaryenCAPI._BinaryenArrayNew( + this.module.ptr, + i32ArrayTypeInfo.heapTypeRef, + this.module.i32.const(fields.length), + this.module.i32.const(0), + ), + ); + this.wasmCompiler.currentFuncCtx!.insert(newWasmRawArrayOp); + for (let i = 0; i < fields.length; i++) { + const fieldType = fields[i]; + const predefinedTypeId = + FunctionalFuncs.getPredefinedTypeId(fieldType); + this.wasmCompiler.currentFuncCtx!.insert( + binaryenCAPI._BinaryenArraySet( + this.module.ptr, + this.module.local.get( + wasmRawArrayLocal.index, + wasmRawArrayLocal.type, + ), + this.module.i32.const(i), + this.module.i32.const(predefinedTypeId), + ), + ); + } + const res = this.module.call( + BuiltinNames.getTupleField, + [ + this.module.local.get( + wasmRawArrayLocal.index, + wasmRawArrayLocal.type, + ), + idxI32Ref, + ownerRef, + ], + binaryen.anyref, + ); + return res; + } + } default: throw Error(`wasmIdxGet: ${value}`); } @@ -3928,7 +4052,8 @@ export class WASMExpressionGen { const owner = value.owner as VarValue; const ownerType = owner.type; switch (ownerType.kind) { - case ValueTypeKind.ARRAY: { + case ValueTypeKind.ARRAY: + case ValueTypeKind.WASM_ARRAY: { const ownerRef = this.wasmExprGen(owner); const idxI32Ref = FunctionalFuncs.convertTypeToI32( this.module, @@ -3937,18 +4062,13 @@ export class WASMExpressionGen { const targetValueRef = this.wasmExprGen(value.value!); const ownerHeapTypeRef = this.wasmTypeGen.getWASMHeapType(ownerType); - const arrayOriRef = binaryenCAPI._BinaryenStructGet( - this.module.ptr, - 0, + return FunctionalFuncs.setArrayElemByIdx( + this.module, ownerRef, ownerHeapTypeRef, - false, - ); - return binaryenCAPI._BinaryenArraySet( - this.module.ptr, - arrayOriRef, idxI32Ref, targetValueRef, + ownerType.kind === ValueTypeKind.ARRAY ? false : true, ); } case ValueTypeKind.ANY: { @@ -3986,6 +4106,30 @@ export class WASMExpressionGen { case ValueTypeKind.OBJECT: { return this.elemOp(value); } + case ValueTypeKind.TUPLE: + case ValueTypeKind.WASM_STRUCT: { + const ownerRef = this.wasmExprGen(owner); + /* TODO: _BinaryenStructSet only accept ts number as index, not binaryen.ExpressionRef as index */ + const idxI32Ref = FunctionalFuncs.convertTypeToI32( + this.module, + this.wasmExprGen(value.index), + ); + const targetValueRef = this.wasmExprGen(value.value!); + let idx = 0; + if (value.index instanceof LiteralValue) { + idx = value.index.value as number; + } else { + throw new UnimplementError( + `not sure how to convert idxI32Ref to a regular index yet in wasmElemSet`, + ); + } + return binaryenCAPI._BinaryenStructSet( + this.module.ptr, + idx, + ownerRef, + targetValueRef, + ); + } default: throw Error(`wasmIdxSet: ${value}`); } @@ -4371,8 +4515,8 @@ export class WASMExpressionGen { if ( spreadValue.target.type.kind == ValueTypeKind.ARRAY ) { - const arrayOriHeapType = - this.wasmTypeGen.getWASMArrayOriHeapType( + const arrayOriTypeRef = + this.wasmTypeGen.getWASMArrayOriType( spreadValue.target.type, ); const arrRef = initValueRef; @@ -4383,7 +4527,7 @@ export class WASMExpressionGen { forLoopIdx.index, forLoopIdx.type, ), - arrayOriHeapType, + arrayOriTypeRef, false, ); // box the element by dyntype_new_xxx @@ -4566,15 +4710,25 @@ export class WASMExpressionGen { throw Error('not implemented'); } - private wasmElemsToArr(values: SemanticsValue[], arrType: ArrayType) { + private wasmElemsToArr(values: SemanticsValue[], arrType: ValueType) { const arrayLen = values.length; let elemRefs: binaryen.ExpressionRef[] = []; const srcArrRefs: binaryen.ExpressionRef[] = []; const arrayOriHeapType = - this.wasmTypeGen.getWASMArrayOriHeapType(arrType); + arrType instanceof ArrayType + ? this.wasmTypeGen.getWASMArrayOriHeapType(arrType) + : this.wasmTypeGen.getWASMHeapType(arrType); + const arrayOriTypeRef = + arrType instanceof ArrayType + ? this.wasmTypeGen.getWASMArrayOriType(arrType) + : this.wasmTypeGen.getWASMType(arrType); const arrayStructHeapType = this.wasmTypeGen.getWASMHeapType(arrType); - const elemType = arrType.element; + const elemType = + arrType instanceof ArrayType + ? arrType.element + : (arrType as WASMArrayType).arrayType.element; const statementArray: binaryenCAPI.ExpressionRef[] = []; + let needCopy = false; for (let i = 0; i < arrayLen; i++) { let elemValue = values[i]; if ( @@ -4586,6 +4740,7 @@ export class WASMExpressionGen { } const elemRef = this.wasmExprGen(elemValue); if (elemValue.kind == SemanticsValueKind.SPREAD) { + needCopy = true; if (elemRefs.length != 0) { const elemArrRef = binaryenCAPI._BinaryenArrayNewFixed( this.module.ptr, @@ -4687,7 +4842,7 @@ export class WASMExpressionGen { forLoopIdx.index, forLoopIdx.type, ), - arrayOriHeapType, + arrayOriTypeRef, false, ), ); @@ -4847,60 +5002,74 @@ export class WASMExpressionGen { elemRefs.push(elemRef); } } - if (elemRefs.length != 0) { - const elemArrRef = binaryenCAPI._BinaryenArrayNewFixed( - this.module.ptr, + + const elemArrRef = binaryenCAPI._BinaryenArrayNewFixed( + this.module.ptr, + arrayOriHeapType, + arrayToPtr(elemRefs).ptr, + elemRefs.length, + ); + const elemArrLocal = + this.wasmCompiler.currentFuncCtx!.insertTmpVar(arrayOriTypeRef); + const setElemArrLocalStmt = this.module.local.set( + elemArrLocal.index, + elemArrRef, + ); + const getElemArrLocalStmt = this.module.local.get( + elemArrLocal.index, + elemArrLocal.type, + ); + statementArray.push(setElemArrLocalStmt); + srcArrRefs.push(getElemArrLocalStmt); + elemRefs = []; + + let finalArrRef: binaryen.ExpressionRef; + let finalArrLenRef: binaryen.ExpressionRef; + if (needCopy) { + const resConcatArr = this.wasmArrayConcat( + srcArrRefs, arrayOriHeapType, - arrayToPtr(elemRefs).ptr, - elemRefs.length, + statementArray, ); - const elemArrLocal = this.wasmCompiler.currentFuncCtx!.insertTmpVar( - binaryen.getExpressionType(elemArrRef), + const newArrLenRef = binaryenCAPI._BinaryenArrayLen( + this.module.ptr, + this.module.local.get( + resConcatArr.local.index, + resConcatArr.local.type, + ), + ); + finalArrRef = resConcatArr.ref; + finalArrLenRef = newArrLenRef; + } else { + statementArray.push(getElemArrLocalStmt); + finalArrRef = this.module.block(null, statementArray); + finalArrLenRef = this.module.i32.const(arrayLen); + } + + if (arrType instanceof ArrayType) { + const arrayStructRef = binaryenCAPI._BinaryenStructNew( + this.module.ptr, + arrayToPtr([finalArrRef, finalArrLenRef]).ptr, + 2, + arrayStructHeapType, ); - const setElemArrLocalStmt = this.module.local.set( - elemArrLocal.index, - elemArrRef, + const newArrStructLocal = + this.wasmCompiler.currentFuncCtx!.insertTmpVar( + binaryen.getExpressionType(arrayStructRef), + ); + const setNewArrStructLocal = this.module.local.set( + newArrStructLocal.index, + arrayStructRef, ); - const getElemArrLocalStmt = this.module.local.get( - elemArrLocal.index, - elemArrLocal.type, + const getNewArrStructLocal = this.module.local.get( + newArrStructLocal.index, + newArrStructLocal.type, ); - statementArray.push(setElemArrLocalStmt); - srcArrRefs.push(getElemArrLocalStmt); - elemRefs = []; + this.wasmCompiler.currentFuncCtx!.insert(setNewArrStructLocal); + return getNewArrStructLocal; + } else { + return finalArrRef; } - const resConcatArr = this.wasmArrayConcat( - srcArrRefs, - arrayOriHeapType, - statementArray, - ); - const newArrLenRef = binaryenCAPI._BinaryenArrayLen( - this.module.ptr, - this.module.local.get( - resConcatArr.local.index, - resConcatArr.local.type, - ), - ); - const arrayStructRef = binaryenCAPI._BinaryenStructNew( - this.module.ptr, - arrayToPtr([resConcatArr.ref, newArrLenRef]).ptr, - 2, - arrayStructHeapType, - ); - const newArrStructLocal = - this.wasmCompiler.currentFuncCtx!.insertTmpVar( - binaryen.getExpressionType(arrayStructRef), - ); - const setNewArrStructLocal = this.module.local.set( - newArrStructLocal.index, - arrayStructRef, - ); - const getNewArrStructLocal = this.module.local.get( - newArrStructLocal.index, - newArrStructLocal.type, - ); - this.wasmCompiler.currentFuncCtx!.insert(setNewArrStructLocal); - return getNewArrStructLocal; } private wasmArrayConcat( diff --git a/src/backend/binaryen/wasm_type_gen.ts b/src/backend/binaryen/wasm_type_gen.ts index 9bfb7551..ac4c495c 100644 --- a/src/backend/binaryen/wasm_type_gen.ts +++ b/src/backend/binaryen/wasm_type_gen.ts @@ -35,10 +35,13 @@ import { FunctionType, ObjectType, Primitive, + TupleType, TypeParameterType, UnionType, ValueType, ValueTypeKind, + WASMArrayType, + WASMStructType, } from '../../semantics/value_types.js'; import { UnimplementError } from '../../error.js'; import { @@ -51,6 +54,12 @@ import { BuiltinNames } from '../../../lib/builtin/builtin_name.js'; import { VarValue } from '../../semantics/value.js'; import { needSpecialized } from '../../semantics/type_creator.js'; import { getConfig } from '../../../config/config_mgr.js'; +import { + MutabilityKind, + NullabilityKind, + PackedTypeKind, +} from '../../utils.js'; +import { typeInfo } from './glue/utils.js'; export class WASMTypeGen { private typeMap: Map = new Map(); @@ -159,6 +168,13 @@ export class WASMTypeGen { case ValueTypeKind.ENUM: this.createWASMEnumType(type); break; + case ValueTypeKind.TUPLE: + this.createWASMTupleType(type); + break; + case ValueTypeKind.WASM_ARRAY: + case ValueTypeKind.WASM_STRUCT: + this.createWASMRawType(type); + break; default: throw new UnimplementError(`createWASMType: ${type}`); } @@ -501,6 +517,187 @@ export class WASMTypeGen { this.typeMap.set(type, this.getWASMValueType(type.memberType)); } + createWASMTupleType(type: TupleType) { + const fieldTypesListRef = new Array(); + for (const elementType of type.elements) { + fieldTypesListRef.push(this.getWASMValueType(elementType)); + } + const fieldPackedTypesListRef = new Array( + fieldTypesListRef.length, + ).fill(Packed.Not); + const fieldMutablesListRef = new Array( + fieldTypesListRef.length, + ).fill(true); + + const tb = binaryenCAPI._TypeBuilderCreate(1); + const buildIndex = this.createTbIndexForType(type); + const tupleTypeInfo = initStructType( + fieldTypesListRef, + fieldPackedTypesListRef, + fieldMutablesListRef, + fieldTypesListRef.length, + true, + buildIndex, + tb, + ); + + this.typeMap.set(type, tupleTypeInfo.typeRef); + this.heapTypeMap.set(type, tupleTypeInfo.heapTypeRef); + } + + createWASMRawType(type: ValueType) { + if (this.typeMap.has(type)) { + return; + } + + switch (type.kind) { + case ValueTypeKind.WASM_ARRAY: + this.createWASMArrayRawType(type); + break; + + case ValueTypeKind.WASM_STRUCT: + this.createWASMStructRawType(type); + break; + + default: + break; + } + } + + createWASMArrayRawType(type: WASMArrayType) { + let arrRawTypeRef: binaryen.Type; + let arrRawHeapTypeRef: binaryenCAPI.HeapTypeRef; + let arrayRawTypeInfo: typeInfo; + if ( + type.packedTypeKind === PackedTypeKind.Not_Packed && + type.mutability === MutabilityKind.Mutable && + type.nullability === NullabilityKind.Nullable + ) { + arrRawTypeRef = this.getWASMArrayOriType(type.arrayType); + arrRawHeapTypeRef = this.getWASMArrayOriHeapType(type.arrayType); + arrayRawTypeInfo = { + typeRef: arrRawTypeRef, + heapTypeRef: arrRawHeapTypeRef, + }; + } else { + const elemTypeRef = this.getWASMValueType(type.arrayType.element); + let elementPackedType: binaryenCAPI.PackedType = Packed.Not; + switch (type.packedTypeKind) { + case PackedTypeKind.I8: { + elementPackedType = Packed.I8; + break; + } + case PackedTypeKind.I16: { + elementPackedType = Packed.I16; + break; + } + } + let elementMutable: binaryenCAPI.bool = true; + if (type.mutability === MutabilityKind.Immutable) { + elementMutable = false; + } + let nullable: binaryenCAPI.bool = true; + if (type.nullability === NullabilityKind.NonNullable) { + nullable = false; + } + const tb = binaryenCAPI._TypeBuilderCreate(1); + const buildIndex = this.createTbIndexForType(type.arrayType); + arrayRawTypeInfo = initArrayType( + elemTypeRef, + elementPackedType, + elementMutable, + nullable, + buildIndex, + tb, + ); + } + + this.typeMap.set(type, arrayRawTypeInfo.typeRef); + this.heapTypeMap.set(type, arrayRawTypeInfo.heapTypeRef); + } + + createWASMStructRawType(type: WASMStructType) { + let structRawTypeRef: binaryen.Type; + let structRawHeapTypeRef: binaryenCAPI.HeapTypeRef; + let structRawTypeInfo: typeInfo; + const isEachFieldNotPacked = type.packedTypeKinds.every( + (value) => value === PackedTypeKind.Not_Packed, + ); + const isEachFieldMutable = type.mutabilitys.every( + (value) => value === MutabilityKind.Mutable, + ); + const isNullable = + type.nullability === NullabilityKind.Nullable ? true : false; + if ( + isEachFieldNotPacked && + isEachFieldMutable && + isNullable && + !type.baseType + ) { + structRawTypeRef = this.getWASMType(type.tupleType); + structRawHeapTypeRef = this.getWASMHeapType(type.tupleType); + structRawTypeInfo = { + typeRef: structRawTypeRef, + heapTypeRef: structRawHeapTypeRef, + }; + } else { + const fieldTypesListRef = new Array(); + for (const elementType of type.tupleType.elements) { + fieldTypesListRef.push(this.getWASMValueType(elementType)); + } + const fieldPackedTypesListRef = new Array( + fieldTypesListRef.length, + ); + for (const packedType of type.packedTypeKinds) { + let fieldPackedType = Packed.Not; + switch (packedType) { + case PackedTypeKind.I8: { + fieldPackedType = Packed.I8; + break; + } + case PackedTypeKind.I16: { + fieldPackedType = Packed.I16; + break; + } + } + fieldPackedTypesListRef.push(fieldPackedType); + } + const fieldMutablesListRef = new Array( + fieldTypesListRef.length, + ); + for (const mutability of type.mutabilitys) { + let fieldMutability = true; + if (mutability === MutabilityKind.Immutable) { + fieldMutability = false; + } + fieldMutablesListRef.push(fieldMutability); + } + let nullable = true; + if (type.nullability === NullabilityKind.NonNullable) { + nullable = false; + } + const baseTypeRef = type.baseType + ? this.getWASMType(type.baseType) + : undefined; + + const tb = binaryenCAPI._TypeBuilderCreate(1); + const buildIndex = this.createTbIndexForType(type.tupleType); + structRawTypeInfo = initStructType( + fieldTypesListRef, + fieldPackedTypesListRef, + fieldMutablesListRef, + fieldTypesListRef.length, + nullable, + buildIndex, + tb, + baseTypeRef, + ); + } + + this.typeMap.set(type, structRawTypeInfo.typeRef); + this.heapTypeMap.set(type, structRawTypeInfo.heapTypeRef); + } + getObjSpecialSuffix(type: ArrayType) { let specialType: ValueType | undefined = undefined; if (type.specialTypeArguments && type.specialTypeArguments.length > 0) { diff --git a/src/error.ts b/src/error.ts index f946382f..c4fcee9e 100644 --- a/src/error.ts +++ b/src/error.ts @@ -50,3 +50,9 @@ export class StatementError extends Error { super('[StatementError]\n' + message); } } + +export class CommentError extends Error { + constructor(message: string) { + super('[CommentError]\n' + message); + } +} diff --git a/src/scope.ts b/src/scope.ts index e688a5ba..a1b1923c 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -23,7 +23,7 @@ import { Import, Export, parseComment, - parseCommentBasedNode, + parseCommentBasedFuncNode, } from './utils.js'; import { Parameter, Variable } from './variable.js'; import { Statement } from './statement.js'; @@ -832,7 +832,7 @@ export class ScopeScanner { ) { const parentScope = this.currentScope!; const functionScope = new FunctionScope(parentScope); - parseCommentBasedNode(node, functionScope); + parseCommentBasedFuncNode(node, functionScope); if (node.modifiers !== undefined) { for (const modifier of node.modifiers) { functionScope.addModifier(modifier); @@ -1189,7 +1189,7 @@ export class ScopeScanner { private _generateFuncScope(node: ts.FunctionLikeDeclaration) { const parentScope = this.currentScope!; const functionScope = new FunctionScope(parentScope); - parseCommentBasedNode(node, functionScope); + parseCommentBasedFuncNode(node, functionScope); if (node.modifiers !== undefined) { for (const modifier of node.modifiers) { functionScope.addModifier(modifier); diff --git a/src/semantic_check.ts b/src/semantic_check.ts index 13eaa18c..bcccdb50 100644 --- a/src/semantic_check.ts +++ b/src/semantic_check.ts @@ -201,6 +201,9 @@ export default class SemanticChecker { ErrorFlag.ArgsAndParamsTypesAreNominalClass, `argument type and parameter type are nominal class types`, ); + if (!argExpr.exprType) { + console.log('hh'); + } this.voidTypeCheck(argExpr.exprType.kind); if (argExpr instanceof PropertyAccessExpression) { this.invokeAnyObjCheck( diff --git a/src/semantics/expression_builder.ts b/src/semantics/expression_builder.ts index 0a359d2f..5bb8b057 100644 --- a/src/semantics/expression_builder.ts +++ b/src/semantics/expression_builder.ts @@ -19,6 +19,9 @@ import { EnumType, ObjectType, ValueTypeWithArguments, + WASMArrayType, + TupleType, + WASMStructType, } from './value_types.js'; import { PredefinedTypeId, getNodeLoc, isTypeGeneric } from '../utils.js'; import { Logger } from '../log.js'; @@ -32,6 +35,7 @@ import { createObjectType, SpecializeTypeMapper, CreateWideTypeFromTypes, + createTupleType, } from './type_creator.js'; import { GetPredefinedType } from './predefined_types.js'; @@ -148,7 +152,15 @@ import { importSearchTypes, } from '../scope.js'; -import { Type, TSClass, TypeKind, TSArray } from '../type.js'; +import { + Type, + TSClass, + TypeKind, + TSArray, + TSTuple, + WasmStructType, + WasmArrayType, +} from '../type.js'; import { BuildContext, @@ -173,6 +185,7 @@ import { import { processEscape } from '../utils.js'; import { BuiltinNames } from '../../lib/builtin/builtin_name.js'; import { getConfig } from '../../config/config_mgr.js'; +import { UnimplementError } from '../error.js'; function isInt(expr: Expression): boolean { /* TODO: currently we treat all numbers as f64, we can make some analysis and optimize some number to int */ @@ -533,7 +546,17 @@ function createDynamicAccess( } if (is_write) return new DynamicSetValue(own, name); - return new DynamicGetValue(own, name, is_method_call); + let type: ValueType | undefined = undefined; + if ( + own.type.kind === ValueTypeKind.WASM_ARRAY || + own.type.kind === ValueTypeKind.WASM_STRUCT || + own.type.kind === ValueTypeKind.TUPLE + ) { + if (name === 'length') { + type = Primitive.Number; + } + } + return new DynamicGetValue(own, name, is_method_call, type); } function createShapeAccess( @@ -936,36 +959,50 @@ function buildArrayLiteralExpression( if ( expr.exprType instanceof TSArray && expr.exprType.elementType.kind == TypeKind.UNKNOWN - ) + ) { return new NewArrayLenValue( GetPredefinedType(PredefinedTypeId.ARRAY_ANY)! as ArrayType, new LiteralValue(Primitive.Int, 0), ); + } } const init_values: SemanticsValue[] = []; - let array_type = context.findValueType(expr.exprType); - - let init_types: Set | undefined = undefined; - if (!array_type || array_type.kind != ValueTypeKind.ARRAY) { - init_types = new Set(); + let arrayLiteral_type = context.findValueType(expr.exprType); + /* ArrayLiteral may be array type, and it can be tuple type */ + let init_array_types: Set | undefined = undefined; + if (!arrayLiteral_type || arrayLiteral_type.kind != ValueTypeKind.ARRAY) { + init_array_types = new Set(); } // element type calculated from exprType - let element_type: ValueType | undefined; - if (array_type instanceof ArrayType) { - element_type = (array_type).element; + let element_type: ValueType | undefined = undefined; + if (arrayLiteral_type instanceof ArrayType) { + element_type = (arrayLiteral_type).element; + } + if (expr.exprType instanceof WasmArrayType) { + element_type = createType(context, expr.exprType.arrayType.elementType); } - for (const element of expr.arrayValues) { + for (let i = 0; i < expr.arrayValues.length; i++) { + const element = expr.arrayValues[i]; context.pushReference(ValueReferenceKind.RIGHT); let v = buildExpression(element, context); - if (element_type != undefined) { + /* get element type if exprType is TSTuple */ + if (expr.exprType instanceof TSTuple) { + element_type = createType(context, expr.exprType.elements[i]); + } else if (expr.exprType instanceof WasmStructType) { + element_type = createType( + context, + expr.exprType.tupleType.elements[i], + ); + } + if (element_type !== undefined) { v = newCastValue(element_type, v); } context.popReference(); init_values.push(v); - // if v is SpreadValue, add it's elem-type to init_types + /* if v is SpreadValue, add it's elem-type to init_types */ let v_type = v.type; if (v instanceof SpreadValue) { const target = v.target; @@ -975,39 +1012,54 @@ function buildArrayLiteralExpression( v_type = target.type; } } - if (init_types) { - init_types.add(v_type); + if (init_array_types) { + init_array_types.add(v_type); } } - if (init_types) { - array_type = createArrayType( + if (init_array_types) { + arrayLiteral_type = createArrayType( context, - CreateWideTypeFromTypes(context, init_types), + CreateWideTypeFromTypes(context, init_array_types), ); } - const elem_type = (array_type as ArrayType).element; - const initValues = - expr.arrayValues.length == 0 - ? [] - : init_values.map((v) => { - return elem_type.equals(v.type) - ? v - : newCastValue(elem_type, v); - }); - // process generic array type - if (initValues.length > 0) { - // actual element type - const value_type = initValues[0].type; - if ( - elem_type.kind == ValueTypeKind.TYPE_PARAMETER && - !value_type.equals(elem_type) - ) - array_type = createArrayType(context, value_type); + if (expr.exprType instanceof TSTuple) { + return new NewLiteralArrayValue( + createType(context, expr.exprType), + init_values, + ); + } else if (expr.exprType instanceof WasmStructType) { + return new NewLiteralArrayValue( + createType(context, expr.exprType), + init_values, + ); + } else { + const elem_type = (arrayLiteral_type as ArrayType).element; + const initValues = + expr.arrayValues.length == 0 + ? [] + : init_values.map((v) => { + return elem_type.equals(v.type) + ? v + : newCastValue(elem_type, v); + }); + /* process generic array type */ + if (initValues.length > 0) { + // actual element type + const value_type = initValues[0].type; + if ( + elem_type.kind == ValueTypeKind.TYPE_PARAMETER && + !value_type.equals(elem_type) + ) + arrayLiteral_type = createArrayType(context, value_type); + } + let literalArrayType = arrayLiteral_type!; + if (expr.exprType instanceof WasmArrayType) { + literalArrayType = createType(context, expr.exprType); + } + return new NewLiteralArrayValue(literalArrayType, initValues); } - - return new NewLiteralArrayValue(array_type!, initValues); } export function isEqualOperator(kind: ts.SyntaxKind): boolean { @@ -1743,6 +1795,11 @@ export function newBinaryExprValue( /* For NewArrayLenValue with zero length, update the array type according to the assign target */ right_value.type = left_value.type; + } else if ( + left_value.effectType instanceof WASMArrayType && + right_value.type instanceof ArrayType + ) { + /* In this situation, we want to create a raw wasm array, no need to cast */ } else { right_value = newCastValue(left_value.effectType, right_value); if (isMemberSetValue(left_value)) { @@ -1791,7 +1848,9 @@ function isMemberSetValue(v: SemanticsValue): boolean { v.kind == SemanticsValueKind.STRING_INDEX_SET || v.kind == SemanticsValueKind.ARRAY_INDEX_SET || v.kind == SemanticsValueKind.OBJECT_INDEX_SET || - v.kind == SemanticsValueKind.OBJECT_KEY_SET + v.kind == SemanticsValueKind.OBJECT_KEY_SET || + v.kind == SemanticsValueKind.WASMARRAY_INDEX_SET || + v.kind == SemanticsValueKind.WASMSTRUCT_INDEX_SET ); } @@ -2386,19 +2445,26 @@ function buildNewExpression2( } const clazz_type = object_type.classType; + let obj_type: WASMArrayType | ArrayType; + if (expr.exprType instanceof WasmArrayType) { + obj_type = createType(context, expr.exprType) as WASMArrayType; + } else { + obj_type = object_type as ArrayType; + } + if (clazz_type && clazz_type.kind == ValueTypeKind.ARRAY) { if (expr.lenExpr != null) { const lenExpr = buildExpression(expr.lenExpr!, context); - const object_value = new NewArrayLenValue( - object_type as ArrayType, - lenExpr, - ); - if (valueTypeArgs) object_value.setTypeArguments(valueTypeArgs); + const object_value = new NewArrayLenValue(obj_type, lenExpr); + if (valueTypeArgs) + (object_value).setTypeArguments( + valueTypeArgs, + ); return object_value; } else { return buildNewArrayParameters( context, - object_type as ArrayType, + obj_type, expr.newArgs, valueTypeArgs, ); @@ -2466,7 +2532,7 @@ function buildNewClass( function buildNewArrayParameters( context: BuildContext, - arr_type: ArrayType, + arr_type: ArrayType | WASMArrayType, params: Expression[] | undefined, valueTypeArgs: ValueType[] | undefined, ): SemanticsValue { @@ -2480,8 +2546,15 @@ function buildNewArrayParameters( if (params && params && params.length > 0) { for (const p of params) { context.pushReference(ValueReferenceKind.RIGHT); - const v = buildExpression(p, context); + let v = buildExpression(p, context); context.popReference(); + const elem_type = + arr_type instanceof ArrayType + ? arr_type.element + : (arr_type).arrayType.element; + if (v.type !== elem_type) { + v = newCastValue(elem_type, v); + } param_values.push(v); if (init_types) { init_types.add(v.type); @@ -2489,22 +2562,32 @@ function buildNewArrayParameters( } } - if (init_types) { - arr_type = createArrayType( - context, - CreateWideTypeFromTypes(context, init_types), - ); - } + let arr_value: SemanticsValue; + if (arr_type instanceof ArrayType) { + if (init_types) { + arr_type = createArrayType( + context, + CreateWideTypeFromTypes(context, init_types), + ); + } - if (!arr_type.isSpecialized()) { - arr_type = GetPredefinedType(PredefinedTypeId.ARRAY_ANY)! as ArrayType; + if (!arr_type.isSpecialized()) { + arr_type = GetPredefinedType( + PredefinedTypeId.ARRAY_ANY, + )! as ArrayType; + } + + arr_value = new NewArrayValue( + (arr_type).instanceType! as ArrayType, + param_values, + ); + if (valueTypeArgs) { + (arr_value).setTypeArguments(valueTypeArgs); + } + } else { + arr_value = new NewArrayValue(arr_type as WASMArrayType, param_values); } - const arr_value = new NewArrayValue( - arr_type.instanceType! as ArrayType, - param_values, - ); - if (valueTypeArgs) arr_value.setTypeArguments(valueTypeArgs); return arr_value; } @@ -2613,6 +2696,27 @@ function buildElementAccessExpression( ? SemanticsValueKind.OBJECT_INDEX_SET : SemanticsValueKind.OBJECT_INDEX_GET; } + } else if (type.kind === ValueTypeKind.TUPLE) { + element_type = is_set + ? SemanticsValueKind.TUPLE_INDEX_SET + : SemanticsValueKind.TUPLE_INDEX_GET; + if (arg instanceof LiteralValue) { + const index = arg.value as number; + value_type = (type as TupleType).elements[index]; + } + } else if (type.kind === ValueTypeKind.WASM_ARRAY) { + element_type = is_set + ? SemanticsValueKind.WASMARRAY_INDEX_SET + : SemanticsValueKind.WASMARRAY_INDEX_GET; + value_type = (type as WASMArrayType).arrayType.element; + } else if (type.kind === ValueTypeKind.WASM_STRUCT) { + element_type = is_set + ? SemanticsValueKind.WASMSTRUCT_INDEX_SET + : SemanticsValueKind.WASMSTRUCT_INDEX_GET; + if (arg instanceof LiteralValue) { + const index = arg.value as number; + value_type = (type as WASMStructType).tupleType.elements[index]; + } } } else { if (type.kind == ValueTypeKind.OBJECT) { diff --git a/src/semantics/semantics_nodes.ts b/src/semantics/semantics_nodes.ts index e77a92b8..9ea56eb3 100644 --- a/src/semantics/semantics_nodes.ts +++ b/src/semantics/semantics_nodes.ts @@ -25,6 +25,7 @@ import { TypeKind, TSFunction, TSArray, + TSTuple, } from '../type.js'; import { @@ -40,6 +41,7 @@ import { EnumType, ObjectType, WASM, + TupleType, } from './value_types.js'; import { GetPredefinedType } from './predefined_types.js'; @@ -881,6 +883,42 @@ export class ModuleNode extends SemanticsNode { } } } + break; + } + case TypeKind.TUPLE: { + let isEqual = false; + for (const t of this.types.values()) { + Logger.debug(`==== t: ${t}, TupleType: ${type}`); + if ( + t.kind == ValueTypeKind.TUPLE && + (t as TupleType).elements.length === + (type as TSTuple).elements.length + ) { + for ( + let i = 0; + i < (type as TSTuple).elements.length; + i++ + ) { + const elemType = this.findValueTypeByType( + (type as TSTuple).elements[i], + ); + if ( + !elemType || + (t as TupleType).elements[i].typeId !== + elemType.typeId + ) { + isEqual = false; + break; + } else { + isEqual = true; + } + } + if (isEqual) { + return t; + } + } + } + break; } } return valueType; @@ -909,6 +947,21 @@ export class ModuleNode extends SemanticsNode { return undefined; } + findTupleElementTypes(elementTypes: ValueType[]): ValueType | undefined { + for (const t of this.types.values()) { + if ( + t.kind == ValueTypeKind.TUPLE && + (t as TupleType).elements.toString() === elementTypes.toString() + ) { + Logger.debug( + `==== t: ${t}, elementTypes in tupleType: ${elementTypes}`, + ); + return t; + } + } + return undefined; + } + findObjectValueType(tsclass: TSClass): ValueType | undefined { for (const t of this.types.keys()) { if (!(t instanceof TSClass)) { diff --git a/src/semantics/type_creator.ts b/src/semantics/type_creator.ts index 36d04c34..e8e7c076 100644 --- a/src/semantics/type_creator.ts +++ b/src/semantics/type_creator.ts @@ -17,6 +17,9 @@ import { TSEnum, TSContext, TSTypeWithArguments, + WasmArrayType, + TSTuple, + WasmStructType, } from '../type.js'; import { InternalNames } from './internal.js'; @@ -47,6 +50,9 @@ import { ObjectTypeFlag, ClosureContextType, ValueTypeWithArguments, + WASMArrayType, + TupleType, + WASMStructType, } from './value_types.js'; import { BuildContext } from './builder_context.js'; @@ -102,6 +108,17 @@ export function createArrayType( return specializeBuiltinObjectType('Array', [element_type])! as ArrayType; } +export function createTupleType( + context: BuildContext, + element_types: ValueType[], +): TupleType { + const tuple_type = context.module.findTupleElementTypes(element_types); + if (tuple_type) { + return tuple_type as TupleType; + } + return new TupleType(context.nextTypeId(), element_types); +} + function createTypeScores(): Map { const m = new Map(); @@ -391,6 +408,19 @@ export function createType( value_type = enum_type; break; } + case TypeKind.TUPLE: { + const tsTuple = type as TSTuple; + const tuple_elements: ValueType[] = []; + for (const element_type of tsTuple.elements) { + tuple_elements.push(createType(context, element_type)); + } + const tuple_type = new TupleType( + context.nextTypeId(), + tuple_elements, + ); + value_type = tuple_type; + break; + } case TypeKind.CONTEXT: { const contextType = type as TSContext; const parentCtxType = contextType.parentCtxType @@ -406,6 +436,40 @@ export function createType( value_type = new ClosureContextType(parentCtxType, freeVarTypeList); break; } + case TypeKind.WASM_ARRAY: { + const wasmArrayType = type as WasmArrayType; + const arrayValueType = createType( + context, + wasmArrayType.arrayType, + ) as ArrayType; + value_type = new WASMArrayType( + arrayValueType, + wasmArrayType.packedTypeKind, + wasmArrayType.mutability, + wasmArrayType.nullability, + ); + break; + } + case TypeKind.WASM_STRUCT: { + const wasmStructType = type as WasmStructType; + const structValueType = createType( + context, + wasmStructType.tupleType, + ) as TupleType; + value_type = new WASMStructType( + structValueType, + wasmStructType.packedTypeKinds, + wasmStructType.mutabilitys, + wasmStructType.nullability, + wasmStructType.baseType + ? (createType( + context, + wasmStructType.baseType, + ) as WASMStructType) + : undefined, + ); + break; + } } if (value_type) { diff --git a/src/semantics/value.ts b/src/semantics/value.ts index fffe8be0..6586164e 100644 --- a/src/semantics/value.ts +++ b/src/semantics/value.ts @@ -18,6 +18,7 @@ import { TypeParameterType, ObjectType, WASMType, + WASMArrayType, } from './value_types.js'; import { PredefinedTypeId, SourceLocation } from '../utils.js'; import { SymbolKeyToString } from './builder_context.js'; @@ -105,6 +106,12 @@ export enum SemanticsValueKind { ARRAY_INDEX_SET, OBJECT_INDEX_GET, OBJECT_INDEX_SET, + TUPLE_INDEX_GET, + TUPLE_INDEX_SET, + WASMARRAY_INDEX_GET, + WASMARRAY_INDEX_SET, + WASMSTRUCT_INDEX_GET, + WASMSTRUCT_INDEX_SET, OBJECT_KEY_GET, OBJECT_KEY_SET, @@ -114,6 +121,8 @@ export enum SemanticsValueKind { NEW_ARRAY, NEW_ARRAY_LEN, NEW_FROM_CLASS_OBJECT, + NEW_RAW_ARRAY, + NEW_RAW_ARRAY_LEN, // FOR flatten BLOCK, @@ -609,7 +618,10 @@ export type ElementGetValueKind = | SemanticsValueKind.STRING_INDEX_GET | SemanticsValueKind.ARRAY_INDEX_GET | SemanticsValueKind.OBJECT_KEY_GET - | SemanticsValueKind.ENUM_KEY_GET; + | SemanticsValueKind.ENUM_KEY_GET + | SemanticsValueKind.TUPLE_INDEX_GET + | SemanticsValueKind.WASMARRAY_INDEX_GET + | SemanticsValueKind.WASMSTRUCT_INDEX_GET; export class ElementGetValue extends SemanticsValue { constructor( @@ -640,7 +652,10 @@ export class ElementGetValue extends SemanticsValue { export type ElementSetValueKind = | SemanticsValueKind.STRING_INDEX_SET | SemanticsValueKind.ARRAY_INDEX_SET - | SemanticsValueKind.OBJECT_KEY_SET; + | SemanticsValueKind.OBJECT_KEY_SET + | SemanticsValueKind.TUPLE_INDEX_SET + | SemanticsValueKind.WASMARRAY_INDEX_SET + | SemanticsValueKind.WASMSTRUCT_INDEX_SET; export class ElementSetValue extends SemanticsValue { constructor( @@ -810,8 +825,9 @@ export class DynamicGetValue extends SemanticsValue { public owner: SemanticsValue, public name: string, public isMethodCall: boolean, + type?: ValueType, ) { - super(SemanticsValueKind.DYNAMIC_GET, Primitive.Any); + super(SemanticsValueKind.DYNAMIC_GET, type ? type : Primitive.Any); } toString(): string { @@ -1170,8 +1186,9 @@ export class NewLiteralObjectValue extends SemanticsValue { export class NewLiteralArrayValue extends SemanticsValue { constructor(type: ValueType, public initValues: SemanticsValue[]) { super(SemanticsValueKind.NEW_LITERAL_ARRAY, type); - // TODO get the shape - this.shape = (type as ArrayType).instanceType!.meta.originShape; + if (type instanceof ArrayType) { + this.shape = (type as ArrayType).instanceType!.meta.originShape; + } } } @@ -1216,15 +1233,20 @@ export class NewConstructorObjectValue extends SemanticsValue { } export class NewArrayValue extends NewConstructorObjectValue { - constructor(type: ArrayType, parameters: SemanticsValue[]) { + constructor(type: ArrayType | WASMArrayType, parameters: SemanticsValue[]) { super(type, parameters, SemanticsValueKind.NEW_ARRAY); } } export class NewArrayLenValue extends SemanticsValue { - constructor(type: ArrayType, public readonly len: SemanticsValue) { + constructor( + type: ArrayType | WASMArrayType, + public readonly len: SemanticsValue, + ) { super(SemanticsValueKind.NEW_ARRAY_LEN, type); - this.shape = type.meta.originShape; + if (type instanceof ArrayType) { + this.shape = type.meta.originShape; + } } private _typeArguments?: ValueType[]; diff --git a/src/semantics/value_types.ts b/src/semantics/value_types.ts index 088de955..6d4c44e1 100644 --- a/src/semantics/value_types.ts +++ b/src/semantics/value_types.ts @@ -4,7 +4,13 @@ */ import { ObjectDescription, UnknownObjectDescription } from './runtime.js'; -import { DefaultTypeId, PredefinedTypeId } from '../utils.js'; +import { + DefaultTypeId, + MutabilityKind, + NullabilityKind, + PackedTypeKind, + PredefinedTypeId, +} from '../utils.js'; import { BuiltinNames } from '../../lib/builtin/builtin_name.js'; export enum ValueTypeKind { @@ -34,8 +40,11 @@ export enum ValueTypeKind { EMPTY, TYPE_PARAMETER, // for template type parameter ENUM, + TUPLE, WASM_I64, WASM_F32, + WASM_ARRAY, + WASM_STRUCT, } export class ValueType { @@ -171,6 +180,71 @@ export const WASM = { ANYREF: new WASMType(ValueTypeKind.ANY, PredefinedTypeId.ANY), }; +export class WASMArrayType extends WASMType { + arrayType: ArrayType; + packedTypeKind: PackedTypeKind = PackedTypeKind.Not_Packed; + mutability: MutabilityKind = MutabilityKind.Mutable; + nullability: NullabilityKind = NullabilityKind.Nullable; + + constructor( + arrayType: ArrayType, + packedTypeKind?: PackedTypeKind, + mutability?: MutabilityKind, + nullability?: NullabilityKind, + ) { + super(ValueTypeKind.WASM_ARRAY, PredefinedTypeId.WASM_ARRAY); + this.arrayType = arrayType; + if (packedTypeKind) { + this.packedTypeKind = packedTypeKind; + } + if (mutability) { + this.mutability = mutability; + } + if (nullability) { + this.nullability = nullability; + } + } +} + +export class WASMStructType extends WASMType { + tupleType: TupleType; + packedTypeKinds: PackedTypeKind[]; + mutabilitys: MutabilityKind[]; + nullability: NullabilityKind = NullabilityKind.Nullable; + baseType: WASMStructType | undefined = undefined; + + constructor( + tupleType: TupleType, + packedTypeKinds?: PackedTypeKind[], + mutabilitys?: MutabilityKind[], + nullability?: NullabilityKind, + baseType?: WASMStructType, + ) { + super(ValueTypeKind.WASM_STRUCT, PredefinedTypeId.WASM_STRUCT); + this.tupleType = tupleType; + if (packedTypeKinds) { + this.packedTypeKinds = packedTypeKinds; + } else { + this.packedTypeKinds = new Array( + this.tupleType.elements.length, + ); + this.packedTypeKinds.fill(PackedTypeKind.Not_Packed); + } + if (mutabilitys) { + this.mutabilitys = mutabilitys; + } else { + this.mutabilitys = new Array( + this.tupleType.elements.length, + ); + this.mutabilitys.fill(MutabilityKind.Mutable); + } + if (nullability) { + this.nullability = nullability; + } + this.baseType = baseType; + } +} + export class EmptyType extends ValueType { constructor() { super(ValueTypeKind.EMPTY, PredefinedTypeId.EMPTY); @@ -698,3 +772,17 @@ export class EnumType extends ValueType { return `EnumType[${this.name}](${s})`; } } + +export class TupleType extends ValueType { + constructor(typeId: number, public elements: ValueType[]) { + super(ValueTypeKind.TUPLE, typeId); + } + + toString(): string { + const ts: string[] = []; + for (const t of this.elements) { + ts.push(t.toString()); + } + return `[TUPLE{${this.elements.join(',')}}}](${this.typeId})`; + } +} diff --git a/src/type.ts b/src/type.ts index be3e839c..76f68395 100644 --- a/src/type.ts +++ b/src/type.ts @@ -20,11 +20,17 @@ import { Expression, IdentifierExpression } from './expression.js'; import { Logger } from './log.js'; import { DefaultTypeId, + MutabilityKind, + NullabilityKind, + PackedTypeKind, createClassScopeByClassType, createFunctionScopeByFunctionType, isTypeGeneric, + isWASMArrayComment, + isWASMStructComment, + parseCommentBasedTypeAliasNode, } from './utils.js'; -import { TypeError, UnimplementError } from './error.js'; +import { CommentError, TypeError, UnimplementError } from './error.js'; import { BuiltinNames } from '../lib/builtin/builtin_name.js'; export const enum TypeKind { @@ -41,12 +47,15 @@ export const enum TypeKind { NULL = 'null', INTERFACE = 'interface', UNION = 'unoin', + TUPLE = 'tuple', WASM_I32 = 'i32', WASM_I64 = 'i64', WASM_F32 = 'f32', WASM_F64 = 'f64', WASM_ANYREF = 'anyref', + WASM_ARRAY = 'wasm_array', + WASM_STRUCT = 'wasm_struct', GENERIC = 'generic', UNKNOWN = 'unknown', @@ -101,6 +110,14 @@ export class WasmType extends Type { this.typeKind = TypeKind.WASM_ANYREF; break; } + case TypeKind.WASM_ARRAY: { + this.typeKind = TypeKind.WASM_ARRAY; + break; + } + case TypeKind.WASM_STRUCT: { + this.typeKind = TypeKind.WASM_STRUCT; + break; + } default: { this.typeKind = TypeKind.UNKNOWN; break; @@ -113,6 +130,71 @@ export class WasmType extends Type { } } +export class WasmArrayType extends WasmType { + arrayType: TSArray; + packedTypeKind: PackedTypeKind = PackedTypeKind.Not_Packed; + mutability: MutabilityKind = MutabilityKind.Mutable; + nullability: NullabilityKind = NullabilityKind.Nullable; + + constructor( + arrayType: TSArray, + packedTypeKind?: PackedTypeKind, + mutability?: MutabilityKind, + nullability?: NullabilityKind, + ) { + super(TypeKind.WASM_ARRAY); + this.arrayType = arrayType; + if (packedTypeKind) { + this.packedTypeKind = packedTypeKind; + } + if (mutability) { + this.mutability = mutability; + } + if (nullability) { + this.nullability = nullability; + } + } +} + +export class WasmStructType extends WasmType { + tupleType: TSTuple; + packedTypeKinds: PackedTypeKind[]; + mutabilitys: MutabilityKind[]; + nullability: NullabilityKind = NullabilityKind.Nullable; + baseType: WasmStructType | undefined = undefined; + + constructor( + tupleType: TSTuple, + packedTypeKinds?: PackedTypeKind[], + mutabilitys?: MutabilityKind[], + nullability?: NullabilityKind, + baseType?: WasmStructType, + ) { + super(TypeKind.WASM_STRUCT); + this.tupleType = tupleType; + if (packedTypeKinds) { + this.packedTypeKinds = packedTypeKinds; + } else { + this.packedTypeKinds = new Array( + this.tupleType.elements.length, + ); + this.packedTypeKinds.fill(PackedTypeKind.Not_Packed); + } + if (mutabilitys) { + this.mutabilitys = mutabilitys; + } else { + this.mutabilitys = new Array( + this.tupleType.elements.length, + ); + this.mutabilitys.fill(MutabilityKind.Mutable); + } + if (nullability) { + this.nullability = nullability; + } + this.baseType = baseType; + } +} + export class GenericType extends Type { constructor() { super(); @@ -817,6 +899,29 @@ export class TSEnum extends Type { } } +export class TSTuple extends Type { + typeKind = TypeKind.TUPLE; + private _elements: Type[] = []; + + constructor() { + super(); + } + + get elements(): Array { + return this._elements; + } + + addType(type: Type) { + this._elements.push(type); + } + + toString(): string { + const s: string[] = []; + this._elements.forEach((t) => s.push(t.toString())); + return `Tuple[${s.join(' , ')}]`; + } +} + interface TsCustomType { tsType: ts.Type; customName: string; @@ -828,9 +933,6 @@ export class TypeResolver { currentScope: Scope | null = null; nodeScopeMap: Map; tsTypeMap = new Map(); - tsDeclTypeMap = new Map(); - tsArrayTypeMap = new Map(); - builtInTsTypeMap = new Map(); private symbolTypeMap = new Map(); nodeTypeCache = new Map(); specializedTypeCache = new Map[]>(); @@ -949,7 +1051,7 @@ export class TypeResolver { this.typeIdAllocate(specificType); this.symbolTypeMap.set(node, specificType); this.parsedClassTypes.add(specificType); - this.addTypeToTypeMap(specificType, node); + this.addTypeToScopeTypeMap(specificType, node); } } else { type = this.generateNodeType(symbolNode); @@ -1013,13 +1115,13 @@ export class TypeResolver { customTypeName ? customTypeName : '', type, ); - this.addTypeToTypeMap(type, symbolNode); + this.addTypeToScopeTypeMap(type, symbolNode); break; } case ts.SyntaxKind.ObjectLiteralExpression: { const type = this.symbolTypeMap.get(node)!; this.parseObjectType(node, type as TSClass); - this.addTypeToTypeMap(type, node); + this.addTypeToScopeTypeMap(type, node); break; } case ts.SyntaxKind.ClassDeclaration: { @@ -1028,7 +1130,7 @@ export class TypeResolver { node as ts.ClassDeclaration, classType as TSClass, ); - this.addTypeToTypeMap(classType, node); + this.addTypeToScopeTypeMap(classType, node); break; } case ts.SyntaxKind.InterfaceDeclaration: { @@ -1037,46 +1139,106 @@ export class TypeResolver { node as ts.InterfaceDeclaration, infcType as TSInterface, ); - this.addTypeToTypeMap(type, node); + this.addTypeToScopeTypeMap(type, node); this.symbolTypeMap.set(node, type); return; } case ts.SyntaxKind.UnionType: { - const tsType = this.typechecker!.getTypeFromTypeNode( - node as ts.UnionTypeNode, + const unionTypeNode = node as ts.UnionTypeNode; + const tsType = + this.typechecker!.getTypeFromTypeNode(unionTypeNode); + const type = this.parseUnionType( + tsType as ts.UnionType, + unionTypeNode, ); - const type = this.parseUnionType(tsType as ts.UnionType); - this.addTypeToTypeMap(type, node); + this.addTypeToScopeTypeMap(type, node); break; } case ts.SyntaxKind.EnumDeclaration: { const type = this.parseEnumType(node as ts.EnumDeclaration); - this.addTypeToTypeMap(type, node); + this.addTypeToScopeTypeMap(type, node); break; } case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.FunctionExpression: case ts.SyntaxKind.ArrowFunction: { const type = this.generateNodeType(node); - this.addTypeToTypeMap(type, node); + this.addTypeToScopeTypeMap(type, node); break; } case ts.SyntaxKind.TypeAliasDeclaration: { const typeAliasNode = node; const typeName = typeAliasNode.name.getText(); + let type: Type; const tsType = this.typechecker!.getTypeAtLocation(typeAliasNode); - const type = this.generateNodeType(typeAliasNode.type); - - if ( - tsType.aliasTypeArguments && - type instanceof TSTypeWithArguments - ) { - const typeArgs = tsType.aliasTypeArguments.map((t) => { - return this.tsTypeToType(t) as TSTypeParameter; - }); - type.setTypeParameters(typeArgs); + /* 1. check if typeName is in builtinWasmType */ + if (builtinWasmTypes.has(typeName)) { + type = builtinWasmTypes.get(typeName)!; + } else { + const parseRes = + parseCommentBasedTypeAliasNode(typeAliasNode); + type = this.generateNodeType(typeAliasNode); + /* 2. check if type is WASMArray/WASMStruct type */ + if ( + parseRes && + (isWASMArrayComment(parseRes) || + isWASMStructComment(parseRes)) + ) { + if (isWASMArrayComment(parseRes)) { + if (!(type instanceof TSArray)) { + throw new CommentError( + `${type.toString()} is not array type`, + ); + } + type = new WasmArrayType( + type, + parseRes.packedType, + parseRes.mutability, + parseRes.nullability, + ); + } else { + if (!(type instanceof TSTuple)) { + throw new CommentError( + `${type.toString()} is not tuple type`, + ); + } + let findRes: Type | undefined = undefined; + if (parseRes.baseTypeName) { + findRes = this.currentScope!.findType( + parseRes.baseTypeName, + ); + } + const baseType: WasmStructType | undefined = findRes + ? (findRes as WasmStructType) + : undefined; + type = new WasmStructType( + type, + parseRes.packedTypes, + parseRes.mutabilitys, + parseRes.nullability, + baseType, + ); + } + } else { + /* 3. check if type is TSTypeWithArguments */ + if ( + tsType.aliasTypeArguments && + type instanceof TSTypeWithArguments + ) { + const typeArgs = tsType.aliasTypeArguments.map( + (t) => { + return this.tsTypeToType( + t, + ) as TSTypeParameter; + }, + ); + type.setTypeParameters(typeArgs); + } + } } + + this.addTypeToTSTypeMap(tsType, typeName, type); this.currentScope!.addType(typeName, type); break; } @@ -1244,7 +1406,7 @@ export class TypeResolver { const asExprNode = node; const typeNode = asExprNode.type; const type = this.generateNodeType(typeNode); - this.addTypeToTypeMap(type, node); + this.addTypeToScopeTypeMap(type, node); break; } default: @@ -1287,7 +1449,7 @@ export class TypeResolver { return; } - private addTypeToTypeMap(type: Type, node: ts.Node) { + private addTypeToScopeTypeMap(type: Type, node: ts.Node) { const tsTypeString = this.getTsTypeName(node); if ( @@ -1316,37 +1478,9 @@ export class TypeResolver { } } - getCustomNameOfArrayType( - node: ts.ArrayTypeNode, - customName = 'Array(', - ): string | undefined { - const elemType = node.elementType; - if (ts.isTypeReferenceNode(elemType)) { - const typeRawName = elemType.typeName.getText(); - if (builtinWasmTypes.has(typeRawName)) { - customName += typeRawName; - } else { - return undefined; - } - } else { - if (ts.isArrayTypeNode(elemType)) { - customName += 'Array('; - this.getCustomNameOfArrayType(elemType, customName); - customName += ')'; - } else { - return undefined; - } - } - return customName; - } - getTsTypeRawName(node: ts.Node, isReturnType = false): string | undefined { - if (ts.isArrayTypeNode(node)) { - let customTypeName = this.getCustomNameOfArrayType(node); - if (customTypeName) { - customTypeName += ')'; - } - return customTypeName; + if (ts.isTypeNode(node)) { + return node.getText(); } else if (ts.isTypeReferenceNode(node)) { /* If one node is FunctionLike, then its type will be its return type */ if (!ts.isFunctionLike(node.parent) || isReturnType) { @@ -1378,73 +1512,32 @@ export class TypeResolver { getTypeByInitializer(node: ts.Node): Type | undefined { if (ts.isPropertyAssignment(node)) { const initTypeName = this.getTsTypeRawName(node.initializer); - if (initTypeName && builtinWasmTypes.has(initTypeName)) { - return builtinWasmTypes.get(initTypeName); - } + const initTsType = this.typechecker!.getTypeAtLocation( + node.initializer, + ); + return this.getTypeFromTSTypeMap( + initTsType, + initTypeName ? initTypeName : '', + ); } return undefined; } - getFinalCustomType(type: Type, rawName: string | undefined) { - if (rawName) { - if (builtinWasmTypes.has(rawName)) { - type = builtinWasmTypes.get(rawName)!; - } else { - this.modifyTypeByRawName(type, rawName); - } - } - return type; - } - - modifyTypeByRawName(type: Type, rawName: string) { - if (type instanceof TSArray) { - const arrayOccurrences = rawName.split('Array').filter(Boolean); - const nestedLevel = arrayOccurrences.length; - let targetType: Type | undefined = undefined; - const isBuiltinType = arrayOccurrences.some((elem) => { - const elemTypeNameRegex = elem.match(/([a-zA-Z0-9]+)/); - if (elemTypeNameRegex) { - const elemName = elemTypeNameRegex[0]; - if (builtinWasmTypes.has(elemName)) { - targetType = builtinWasmTypes.get(elemName)!; - return true; - } - } - return false; - }); - if (!isBuiltinType) { - return; - } - let currentType: Type = type; - for (let i = 0; i < nestedLevel; i++) { - if (i == nestedLevel - 1) { - (currentType as TSArray).elementType = targetType!; - } else { - currentType = (currentType as TSArray).elementType; - } - } - } - } - generateNodeType(node: ts.Node): Type { if (!this.typechecker) { this.typechecker = this.parserCtx.typeChecker; } + /* 1. get type from cached type */ const cached_type = this.nodeTypeCache.get(node); if (cached_type) { return cached_type; } - /* Resolve wasm specific type */ - const tsTypeRawName = this.getTsTypeRawName(node); - if (tsTypeRawName && builtinWasmTypes.has(tsTypeRawName)) { - return builtinWasmTypes.get(tsTypeRawName)!; - } - /* For wasmType, some node type should be equal with its initializer type */ + /* 2. For wasmType, some node type should be equal with its initializer type */ const initializerType = this.getTypeByInitializer(node); if (initializerType) { return initializerType; } - + /* 3. special node kind should be parsed as signature */ if (ts.isConstructSignatureDeclaration(node)) { return this.parseSignature( this.typechecker!.getSignatureFromDeclaration( @@ -1473,11 +1566,14 @@ export class TypeResolver { )!, ); } + /* 4. handle this type */ let tsType = this.typechecker!.getTypeAtLocation(node); if ('isThisType' in tsType && (tsType as any).isThisType) { /* For "this" keyword, tsc will inference the actual type */ tsType = this.typechecker!.getDeclaredTypeOfSymbol(tsType.symbol); } + /* 5. customed type should find from tsTypeMap firstly */ + const tsTypeRawName = this.getTsTypeRawName(node); const parsedType = this.getTypeFromTSTypeMap( tsType, tsTypeRawName ? tsTypeRawName : '', @@ -1485,12 +1581,16 @@ export class TypeResolver { if (parsedType) { return parsedType; } - let type = this.tsTypeToType(tsType); - type = this.getFinalCustomType(type, tsTypeRawName); - - /* for example, a: string[] = new Array(), the type of new Array() should be string[] - instead of any[]*/ - if (type instanceof TSArray) { + /* 6. parse tsType to type */ + let type = this.tsTypeToType(tsType, node); + /* 7. set right value's type based on left value's type */ + if (type instanceof TSArray || type instanceof TSTuple) { + /** Example: a: string[] = new Array() + * the type of new Array() should be string[], instead of any[] + */ + /** Example: const a: [i32, string] = [1, 'hi']; + * the type of [1, 'hi'] should be [i32, string], instead of [number, string] + */ const parentNode = node.parent; if ( ts.isVariableDeclaration(parentNode) || @@ -1503,12 +1603,24 @@ export class TypeResolver { ts.isNewExpression(parentNode) || ts.isArrayLiteralExpression(parentNode) ) { - const parentType = this.generateNodeType(parentNode); - if (parentType.kind == TypeKind.ANY) { - type = parentType; + const parentType = this.generateNodeType(parentNode); + if (type instanceof TSArray) { + if (parentType.kind == TypeKind.ANY) { + type = parentType; + } else { + type = (parentType).elementType; + } } else { - type = (this.generateNodeType(parentNode)) - .elementType; + if (ts.isArrayLiteralExpression(parentNode)) { + const parentFieldLength = (parentType).elements + .length; + for (let i = 0; i < parentFieldLength; i++) { + if (parentNode.elements[i] === node) { + type = (parentType).elements[i]; + break; + } + } + } } } } @@ -1516,12 +1628,32 @@ export class TypeResolver { return type; } - public tsTypeToType(type: ts.Type): Type { + public tsTypeToType( + tsType: ts.Type, + tsNode?: ts.Node, + isReturnType = false, + ): Type { let res: Type | undefined; + const typeNode: ts.TypeNode | undefined = tsNode + ? ts.isTypeNode(tsNode) + ? tsNode + : (tsNode as any).type + : undefined; + /* 1. if typeNode is provided, then find from tsTypeMap firstly */ + if (typeNode) { + const tsTypeRawName = this.getTsTypeRawName(typeNode, isReturnType); + const parsedType = this.getTypeFromTSTypeMap( + tsType, + tsTypeRawName ? tsTypeRawName : '', + ); + if (parsedType) { + return parsedType; + } + } - const typeFlag = type.flags; + /* 1. generate type by tsType */ + const typeFlag = tsType.flags; let mask = ts.TypeFlags.Number; - if (typeFlag & mask && !(~typeFlag & mask)) { res = builtinTypes.get('number'); } @@ -1563,9 +1695,9 @@ export class TypeResolver { } mask = ts.TypeFlags.TypeParameter; if (!res && typeFlag & mask && !(~typeFlag & mask)) { - if (type.symbol.declarations) { - const type_name = type.symbol.getName(); - const tp = type.symbol + if (tsType.symbol.declarations) { + const type_name = tsType.symbol.getName(); + const tp = tsType.symbol .declarations[0] as ts.TypeParameterDeclaration; const constraint_node = ts.getEffectiveConstraintOfTypeParameter(tp); @@ -1600,36 +1732,40 @@ export class TypeResolver { return type_param; } } - if (!res && type.symbol && type.symbol.declarations) { - const typeDecl = type.symbol.declarations[0]; + if (!res && tsType.symbol && tsType.symbol.declarations) { + const typeDecl = tsType.symbol.declarations[0]; if (typeDecl.kind == ts.SyntaxKind.EnumDeclaration) { res = this.parseEnumType(typeDecl as ts.EnumDeclaration); } } - if (!res && type.isUnion()) { - res = this.parseUnionType(type); + if (!res && tsType.isUnion()) { + res = this.parseUnionType(tsType, typeNode as ts.UnionTypeNode); } - if (!res && this.isArray(type)) { - if (!type.typeArguments) { + if (!res && this.isArray(tsType)) { + if (!tsType.typeArguments) { throw new TypeError('array type has no type arguments'); } - const elemType = this.tsTypeToType(type.typeArguments![0]); + const elemType = this.tsTypeToType( + tsType.typeArguments![0], + typeNode ? (typeNode as any).elementType : undefined, + ); res = new TSArray(elemType); } if ( !res && - (this.isTypeReference(type) || - this.isInterface(type) || - this.isObjectLiteral(type) || - this.isObjectType(type)) + (this.isTypeReference(tsType) || + this.isInterface(tsType) || + this.isObjectLiteral(tsType) || + this.isObjectType(tsType)) && + tsType.symbol ) { - const decls = type.symbol.declarations; + const decls = tsType.symbol.declarations; if (decls) { - const decl = type.symbol.declarations![0]; - const tsType = this.symbolTypeMap.get(decl); - if (!tsType) { + const decl = tsType.symbol.declarations![0]; + const type = this.symbolTypeMap.get(decl); + if (!type) { throw new TypeError( - `class/interface/object type not found, type name <${type.symbol.name}>. `, + `class/interface/object type not found, type name <${tsType.symbol.name}>. `, ); } // Type references (ObjectFlags.Reference) @@ -1637,11 +1773,11 @@ export class TypeResolver { /* The 'isTypeReference' function uses bit operators to determine whether the type is a Reference * But this result is not always correct */ - this.isTypeReference(type) && - type.objectFlags == ts.ObjectFlags.Reference + this.isTypeReference(tsType) && + tsType.objectFlags == ts.ObjectFlags.Reference ) { const typeArguments = this.typechecker!.getTypeArguments( - type as ts.TypeReference, + tsType as ts.TypeReference, ).map((t) => { return this.tsTypeToType(t); }); @@ -1655,7 +1791,7 @@ export class TypeResolver { // Determine whether a specialized type already exists // If it exists, return it directly - const cache = this.specializedTypeCache.get(tsType); + const cache = this.specializedTypeCache.get(type); if (cache) { let found: Type | undefined; cache.forEach((v) => { @@ -1667,24 +1803,24 @@ export class TypeResolver { } res = TypeResolver.createSpecializedType( - tsType, + type, typeArguments, - tsType as TSClass, + type as TSClass, ); (res as TSClass).setSpecializedArguments(typeArguments); - if (this.specializedTypeCache.has(tsType)) { + if (this.specializedTypeCache.has(type)) { const value = new Map(); value.set(typeSignature, res); - this.specializedTypeCache.get(tsType)!.push(value); + this.specializedTypeCache.get(type)!.push(value); } else { const value = new Map(); value.set(typeSignature, res); - this.specializedTypeCache.set(tsType, [value]); + this.specializedTypeCache.set(type, [value]); } } else { - res = tsType; + res = type; } - } else if (type.symbol.flags & ts.TypeFlags.Substitution) { + } else if (tsType.symbol.flags & ts.TypeFlags.Substitution) { /** TODO: symbol.declarations == undefined, means type always * has TypeFlags.Substitution flag?? */ @@ -1692,38 +1828,75 @@ export class TypeResolver { } else { throw new TypeError( `class/interface/object type contains neither declarations - nor Substitution flag, type name <${type.symbol.name}>. `, + nor Substitution flag, type name <${tsType.symbol.name}>. `, ); } } - if (!res && this.isFunction(type)) { - const signature = type.getCallSignatures()[0]; + if (!res && this.isFunction(tsType)) { + const signature = tsType.getCallSignatures()[0]; res = this.parseSignature(signature); } + if (!res && this.isTupleType(tsType)) { + res = this.parseTupleType( + tsType as ts.TupleType, + typeNode as ts.TupleTypeNode, + ); + } + if (!res) { - Logger.debug(`Encounter un-processed type: ${type.flags}`); + Logger.warn(`Encounter un-processed type: ${tsType.flags}`); res = new Type(); } return res; } - private parseUnionType(type: ts.UnionType): Type { - const union_type = new TSUnion(); - - if (!type.types) { - return builtinTypes.get('any')!; + private parseTupleType( + tsType: ts.TupleType, + tupleTypeNode?: ts.TupleTypeNode, + ) { + const tupleType = new TSTuple(); + if (tupleTypeNode && tupleTypeNode.elements) { + for (const tupleElement of tupleTypeNode.elements) { + const tupleElementType = this.generateNodeType(tupleElement); + tupleType.addType(tupleElementType); + } + } else { + if (!tsType.typeArguments) { + throw new Error(`ts tuple type's typeArguments is undefined`); + } + for (const typeArg of tsType.typeArguments) { + tupleType.addType(this.tsTypeToType(typeArg)); + } } + return tupleType; + } - for (const tstype of type.types) { - union_type.addType(this.tsTypeToType(tstype)); + private parseUnionType( + tsUnionType: ts.UnionType, + unionTypeNode?: ts.UnionTypeNode, + ): Type { + const union_type = new TSUnion(); + /* 1. get type from typeNode firstly */ + if (unionTypeNode && unionTypeNode.types) { + for (const typeNode of unionTypeNode.types) { + const type = this.generateNodeType(typeNode); + union_type.addType(type); + } + } else { + /* 2. get type from tsUnionType */ + if (!tsUnionType.types) { + return builtinTypes.get('any')!; + } + for (const tsType of tsUnionType.types) { + union_type.addType(this.tsTypeToType(tsType)); + } } const types = union_type.types; if (types.every((type) => type === types[0])) { return types[0]; } - // T | null will be treated as nullable T type if (types.find((type) => type.kind === TypeKind.NULL)) { const nonNullTypes = types.filter( @@ -1823,20 +1996,38 @@ export class TypeResolver { } private isObjectLiteral(type: ts.Type) { - return this.isObject(type) && type.symbol.name === '__object'; + return ( + this.isObject(type) && + type.symbol && + type.symbol.name === '__object' + ); } // in most cases, the type has Anonymous ObjectTypeFlag private isObjectType(type: ts.Type) { return ( this.isObject(type) && + type.symbol && type.symbol.name === '__type' && !this.isFunction(type) ); } + private isTupleType(tsType: ts.Type) { + if (this.isTypeReference(tsType)) { + if ((tsType.target.objectFlags & ts.ObjectFlags.Tuple) !== 0) { + return true; + } + } + return false; + } + private isArray(type: ts.Type): type is ts.TypeReference { - return this.isTypeReference(type) && type.symbol.name === 'Array'; + return ( + this.isTypeReference(type) && + type.symbol && + type.symbol.name === 'Array' + ); } private isFunction(type: ts.Type): type is ts.ObjectType { @@ -1961,7 +2152,7 @@ export class TypeResolver { tsFunction.addIsOptionalParam(false); } const tsType = this.typechecker!.getTypeAtLocation(valueDecl); - let customType = this.tsTypeToType(tsType); + let customType = this.tsTypeToType(tsType, (valueDecl as any).type); // e.g. // type ItemGenerator = (item: T, index: U) => void // function test_func(func: ItemGenerator, a: T, b: U) {...} @@ -1973,7 +2164,7 @@ export class TypeResolver { const type = this.typechecker!.getTypeAtLocation(param.type!); if (type.aliasTypeArguments) { const typeArguments = type.aliasTypeArguments!.map((t) => { - return this.tsTypeToType(t); + return this.tsTypeToType(t, param.type); }); customType = TypeResolver.createSpecializedType( customType, @@ -1983,21 +2174,13 @@ export class TypeResolver { } } - /* builtin wasm types */ - const tsTypeRawName = this.getTsTypeRawName(valueDecl); - customType = this.getFinalCustomType(customType, tsTypeRawName); tsFunction.addParamType(customType); }); /* parse return type */ const returnType = signature.getReturnType(); - const customType = this.tsTypeToType(returnType); - /* builtin wasm types */ - const tsTypeRawName = this.getTsTypeRawName(decl, true); - tsFunction.returnType = this.getFinalCustomType( - customType, - tsTypeRawName, - ); + const customType = this.tsTypeToType(returnType, decl.type, true); + tsFunction.returnType = customType; this.nodeTypeCache.set(decl, tsFunction); return tsFunction; diff --git a/src/utils.ts b/src/utils.ts index f36bba6f..56bf9824 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -68,6 +68,8 @@ export enum CommentKind { NativeSignature = 'NativeSignature', Import = 'Import', Export = 'Export', + WASMArray = 'WASMArray', + WASMStruct = 'WASMStruct', } export interface NativeSignature { @@ -84,6 +86,37 @@ export interface Export { exportName: string; } +export enum PackedTypeKind { + Not_Packed = 'Not_Packed', + I8 = 'I8', + I16 = 'I16', +} + +export enum MutabilityKind { + Immutable = 'Immutable', + Mutable = 'Mutable', +} + +export enum NullabilityKind { + NonNullable = 'NonNullable', + Nullable = 'Nullable', +} + +export interface WASMArray { + WASMArray: boolean; + packedType: PackedTypeKind; + mutability: MutabilityKind; + nullability: NullabilityKind; +} + +export interface WASMStruct { + WASMStruct: boolean; + packedTypes?: PackedTypeKind[]; + mutabilitys?: MutabilityKind[]; + nullability?: NullabilityKind; + baseTypeName?: string; +} + export class Stack { private items: T[] = []; push(item: T) { @@ -737,6 +770,9 @@ export enum PredefinedTypeId { DATAVIEW_CONSTRUCTOR, WASM_I64, WASM_F32, + WASM_ARRAY, + WASM_STRUCT, + TUPLE, BUILTIN_TYPE_BEGIN, CUSTOM_TYPE_BEGIN = BUILTIN_TYPE_BEGIN + 1000, @@ -766,6 +802,28 @@ export function isExportComment(obj: any): obj is Export { return obj && 'exportName' in obj; } +export function isWASMArrayComment(obj: any): obj is WASMArray { + return obj && 'WASMArray' in obj; +} + +export function isWASMStructComment(obj: any): obj is WASMStruct { + return obj && 'WASMStruct' in obj; +} + +export function isPackedTypeKind(packedType: string) { + return Object.values(PackedTypeKind).includes(packedType as PackedTypeKind); +} + +export function isMutabilityKind(mutability: string) { + return Object.values(MutabilityKind).includes(mutability as MutabilityKind); +} + +export function isNullabilityKind(nullability: string) { + return Object.values(NullabilityKind).includes( + nullability as NullabilityKind, + ); +} + export function parseComment(commentStr: string) { commentStr = commentStr.replace(/\s/g, ''); if (!commentStr.includes('Wasmnizer-ts')) { @@ -844,6 +902,85 @@ export function parseComment(commentStr: string) { }; return obj; } + case CommentKind.WASMArray: { + const basicArrayInfoReg = commentStr.match(/@WASMArray@\s*/); + if (basicArrayInfoReg === null) { + Logger.error('invalid information in WASMArray comment'); + return null; + } + let packedTypeKind = PackedTypeKind.Not_Packed; + let mutabilityKind = MutabilityKind.Mutable; + let nullabilityKind = NullabilityKind.Nullable; + const arrayInfoReg = commentStr.match( + /@WASMArray@<\s*([^,]+),\s*([^,]+),\s*([^>]+)>/, + ); + if (arrayInfoReg && arrayInfoReg.length === 4) { + Logger.info('use total message of WASMArray comment'); + if ( + !( + isPackedTypeKind(arrayInfoReg[1]) && + isMutabilityKind(arrayInfoReg[2]) && + isNullabilityKind(arrayInfoReg[3]) + ) + ) { + Logger.error('typo error in WASMArray comment'); + return null; + } + packedTypeKind = arrayInfoReg[1] as PackedTypeKind; + mutabilityKind = arrayInfoReg[2] as MutabilityKind; + nullabilityKind = arrayInfoReg[3] as NullabilityKind; + } + const obj: WASMArray = { + WASMArray: true, + packedType: packedTypeKind, + mutability: mutabilityKind, + nullability: nullabilityKind, + }; + return obj; + } + case CommentKind.WASMStruct: { + const basicStructInfoReg = commentStr.match(/@WASMStruct@\s*/); + if (basicStructInfoReg === null) { + Logger.error('invalid information in WASMStruct comment'); + return null; + } + let obj: WASMStruct = { WASMStruct: true }; + const structInfoReg = commentStr.match( + /@WASMStruct@<\s*\[([^>]+)\],\s*\[([^>]+)\],\s*([^,]+),\s*([^>]+)>/, + ); + if (structInfoReg && structInfoReg.length === 5) { + Logger.info('use total message of WASMStruct comment'); + const nullabilityKind = structInfoReg[3]; + if ( + !( + structInfoReg[1] + .split(',') + .every((item) => isPackedTypeKind(item)) && + structInfoReg[2] + .split(',') + .every((item) => isMutabilityKind(item)) && + isNullabilityKind(nullabilityKind) + ) + ) { + Logger.error('typo error in WASMStruct comment'); + return null; + } + const packedTypeKindArray = structInfoReg[1] + .split(',') + .map((item) => item.trim()); + const mutabilityKindArray = structInfoReg[2] + .split(',') + .map((item) => item.trim()); + obj = { + WASMStruct: true, + packedTypes: packedTypeKindArray as PackedTypeKind[], + mutabilitys: mutabilityKindArray as MutabilityKind[], + nullability: nullabilityKind as NullabilityKind, + baseTypeName: structInfoReg[4], + }; + } + return obj; + } default: { Logger.error(`unsupported comment kind ${commentKind}`); return null; @@ -851,21 +988,47 @@ export function parseComment(commentStr: string) { } } -export function parseCommentBasedNode( - node: ts.FunctionLikeDeclaration, - functionScope: FunctionScope, -) { +export function parseCommentBasedNode(node: ts.Node) { const commentRanges = ts.getLeadingCommentRanges( node.getSourceFile().getFullText(), node.getFullStart(), ); + let commentStrings: string[] = []; if (commentRanges?.length) { - const commentStrings: string[] = commentRanges.map((r) => + commentStrings = commentRanges.map((r) => node.getSourceFile().getFullText().slice(r.pos, r.end), ); + } + return commentStrings; +} + +export function parseCommentBasedTypeAliasNode(node: ts.TypeAliasDeclaration) { + const commentStrings = parseCommentBasedNode(node); + if (commentStrings.length > 0) { + /* only the last comment is the valid comment */ + const validComment = commentStrings[commentStrings.length - 1]; + const parseRes = parseComment(validComment); + if (parseRes) { + return parseRes; + } + } + return null; +} + +export function parseCommentBasedFuncNode( + node: ts.FunctionLikeDeclaration, + functionScope: FunctionScope, +) { + const commentStrings = parseCommentBasedNode(node); + if (commentStrings.length > 0) { for (const commentStr of commentStrings) { const parseRes = parseComment(commentStr); - if (parseRes) { + if ( + parseRes && + (isExportComment(parseRes) || + isImportComment(parseRes) || + isNativeSignatureComment(parseRes)) + ) { const idx = functionScope.comments.findIndex((item) => { return ( (isExportComment(item) && isExportComment(parseRes)) || diff --git a/tests/samples/tuple.ts b/tests/samples/tuple.ts new file mode 100644 index 00000000..1ef08c7e --- /dev/null +++ b/tests/samples/tuple.ts @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +export function tuple_type_with_constant() { + const a: [i32, f32] = [10, 20.5]; + const b: [[i32, f32], string] = [a, 'hi']; + console.log(b[0][0]); + console.log(b[0][1]); + console.log(b[1]); +} + +export function tuple_type_with_variable() { + const aaa = { a: 10 }; + type tupleType = [any, string, i32]; + const tupleInstance: tupleType = [aaa, 'hi', 90]; + for (let i = 0; i < tupleInstance.length; i++) { + const field = tupleInstance[i]; + console.log(field); + } +} + +export function tuple_type_nested() { + type tupleType = [string, [i32, boolean, [i64, [f32, any]]]]; + + const tupleInstance: tupleType = ['hi', [10, true, [20, [30, 'hello']]]]; + const a_idx = 1; + const b_idx = 2; + const c_idx = 0; + /* TODO: tuple box to any & unbox from any is not ready */ + // return tupleInstance[a_idx][b_idx][c_idx]; +} + +function tuple_as_param_inner(tuple: [i32, string]) { + console.log(tuple[0]); + console.log(tuple[1]); +} + +export function tuple_as_param() { + const param: [i32, string] = [30, 'hi']; + tuple_as_param_inner(param); +} + +interface I { + a: i32 +} + +function tuple_as_ret_inner(): [I, i64] { + const obj:I = { + a: 10 as i32 + } + const tuple: [I, i64] = [obj, 100] + return tuple; +} + +export function tuple_as_ret() { + const tuple: [I, i64] = tuple_as_ret_inner(); + const obj = tuple[0]; + console.log(obj.a); + console.log(tuple[1]); +} + +export function tuple_as_array_elem() { + const tuple1: [i32, string] = [1, 'hi_1']; + const tuple2: [i32, string] = [2, 'hi_2']; + const tuple3: [i32, string] = [3, 'hi_3']; + const array: [i32, string][] = [tuple1, tuple2, tuple3]; + console.log(array[0][1]); + console.log(array[1][1]); + console.log(array[2][1]); +} + +export function tuple_as_obj_field() { + const tuple1: [i32, string] = [1, 'hi_1']; + const tuple2: [i32, string] = [2, 'hi_2']; + const tuple3: [i32, string] = [3, 'hi_3']; + const obj = { + a: tuple1 as [i32, string], + b: tuple2 as [i32, string], + c: tuple3 as [i32, string], + } + + console.log(obj.a[1]); + console.log(obj.b[1]); + console.log(obj.c[1]); +} + +interface T { + x: [i32, string], + y: [number, string], +} + +export function tuple_as_infc_field() { + const tuple1: [i32, string] = [1, 'hi_1']; + const tuple2: [number, string] = [2, 'hi_2']; + const obj: T = { + x: tuple1 as [i32, string], + y: tuple2, + } + /* TODO: tuple box to any & unbox from any is not ready */ + // console.log(obj.x[1]); + // console.log(obj.y[1]); +} + +export function tuple_with_array() { + const array1: i32[] = [1, 2, 3]; + const array2: string[] = ['hi_1', 'hi_2', 'hi_3']; + const tuple: [i32[], string[]] = [array1, array2]; + console.log(tuple[0][1]); + console.log(tuple[1][1]); +} + +class A { + a: i64 = 1; + b: string = 'hi_1'; +} + +class B { + a: i64 = 2; + b: string = 'hi_2'; +} + +export function tuple_with_class() { + const a_instance = new A(); + const b_instance = new B(); + const tuple: [A, B] = [a_instance, b_instance]; + console.log(tuple[0].a); + console.log(tuple[1].b); +} + +export function tuple_with_infc() { + const tuple1: [i32, string] = [1, 'hi_1']; + const tuple2: [number, string] = [2, 'hi_2']; + const obj: T = { + x: tuple1 as [i32, string], + y: tuple2, + } + const tuple: [T] = [obj]; + /* TODO: tuple box to any & unbox from any is not ready */ + // console.log(tuple[0].x[0]); +} diff --git a/tests/samples/wasmType_heapType.ts b/tests/samples/wasmType_heapType.ts new file mode 100644 index 00000000..48164c96 --- /dev/null +++ b/tests/samples/wasmType_heapType.ts @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +export function wasmArrayTypeWithLiteral() { + // Wasmnizer-ts: @WASMArray@ + type arrayType1 = string[]; + const keys_arr: arrayType1 = ['hi']; + const value1 = keys_arr[0]; + console.log(value1); + + keys_arr[0] = 'hello'; + const value2 = keys_arr[0]; + console.log(value2); + + // Wasmnizer-ts: @WASMArray@ + type arrayType2 = i32[]; + const arr: arrayType2 = [88]; + const value3 = arr[0]; + console.log(value3); + + arr[0] = 77; + const value4 = arr[0]; + console.log(value4); +} + +export function wasmArrayTypeWithNewArray() { + const arrLen = 3; + // Wasmnizer-ts: @WASMArray@ + type arrayType = i64[]; + const wasmArr: arrayType = new Array(arrLen); + for (let i = 0; i < arrLen; i++) { + wasmArr[i] = i; + } + for (let i = 0; i < arrLen; i++) { + console.log(wasmArr[i]); + } + const wasmArr2: arrayType = new Array(8, 9); + for (let i = 0; i < wasmArr2.length; i++) { + console.log(wasmArr2[i]); + } +} + +export function wasmArrayTypeNested(): i64 { + // Wasmnizer-ts: @WASMArray@ + type arrayType1 = i64[]; + const wasmArr1: arrayType1 = [1, 10]; + // Wasmnizer-ts: @WASMArray@ + type arrayType2 = arrayType1[]; + const wasmArr2: arrayType2 = [wasmArr1]; + + return wasmArr2[0][0]; +} + +export function wasmArrayTypeInArray(): i64 { + // Wasmnizer-ts: @WASMArray@ + type arrayType1 = i64[]; + const wasmArr1: arrayType1 = [1, 10]; + + const arr: arrayType1[] = [wasmArr1]; + + return arr[0][0]; +} + +export function wasmArrayTypeInObj(): i64 { + // Wasmnizer-ts: @WASMArray@ + type arrayType1 = i64[]; + const wasmArr1: arrayType1 = [1, 10]; + + const obj = { + a: wasmArr1 as arrayType1 + } + + return obj.a[0]; +} + +export function wasmStructType() { + // Wasmnizer-ts: @WASMArray@ + type arrayType1 = f32[]; + const arr1: arrayType1 = [10]; + + type arrayType2 = arrayType1; + const arr2: arrayType2 = [20]; + + // Wasmnizer-ts: @WASMStruct@ <[Not_Packed, Not_Packed], [Mutable, Mutable], Nullable, NULL> + type structType1 = [arrayType1, i64]; + const struct: structType1 = [arr1, 99]; + + const value1 = struct[0][0]; + console.log(value1); + + const value2 = struct[1]; + console.log(value2); + + // Wasmnizer-ts: @WASMStruct@ + type structType2 = [arrayType2, i32]; + const struct2: structType2 = [arr2, 33]; + + const value3 = struct2[0][0]; + console.log(value3); + + const value4 = struct2[1]; + console.log(value4); + + const value5 = struct2.length; + console.log(value5); +} + +export function wasmStructTypeNested(): i64 { + // Wasmnizer-ts: @WASMStruct@ + type structType1 = [i64]; + const wasmStruct1: structType1 = [1]; + // Wasmnizer-ts: @WASMStruct@ + type structType2 = [structType1]; + const wasmStruct2: structType2 = [wasmStruct1]; + + return wasmStruct2[0][0]; +} + +export function wasmStructTypeInArray(): i64 { + // Wasmnizer-ts: @WASMStruct@ + type structType1 = [i64]; + const wasmStruct1: structType1 = [1]; + + const arr: structType1[] = [wasmStruct1]; + + return arr[0][0]; +} + +export function wasmStructTypeInObj(): i64 { + // Wasmnizer-ts: @WASMStruct@ + type structType1 = [i64]; + const wasmStruct1: structType1 = [1]; + + const obj = { + a: wasmStruct1 as structType1 + } + + return obj.a[0]; +} \ No newline at end of file diff --git a/tests/unit/comment.test.ts b/tests/unit/comment.test.ts index e3aefb0d..3b65448a 100644 --- a/tests/unit/comment.test.ts +++ b/tests/unit/comment.test.ts @@ -12,20 +12,31 @@ import { NativeSignature, } from '../../src/semantics/semantics_nodes.js'; import { FunctionType, WASM } from '../../src/semantics/value_types.js'; -import { builtinTypes } from '../../src/semantics/builtin.js'; +import { + GetBuiltinObjectType, + builtinTypes, +} from '../../src/semantics/builtin.js'; import { Export, Import, + MutabilityKind, NativeSignature as NativeSignatureFrontEnd, + NullabilityKind, + PackedTypeKind, + WASMArray, + WASMStruct, getBuiltinType, isExportComment, isImportComment, isNativeSignatureComment, + isWASMArrayComment, + isWASMStructComment, 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'; +import exp from 'constants'; describe('testParseNativeSignature', function () { it('ARRAYBUFFER_TO_I32', function () { @@ -33,7 +44,7 @@ describe('testParseNativeSignature', function () { 'funcA', FunctionOwnKind.DEFAULT, new FunctionType(-1, WASM.I32, [ - builtinTypes.get('ArrayBuffer')!, + GetBuiltinObjectType('ArrayBuffer'), WASM.I32, ]), new BlockNode([]), @@ -71,7 +82,7 @@ describe('testParseNativeSignature', function () { 'funcA', FunctionOwnKind.DEFAULT, new FunctionType(-1, WASM.I32, [ - builtinTypes.get('ArrayBuffer')!, + GetBuiltinObjectType('ArrayBuffer'), WASM.I32, ]), new BlockNode([]), @@ -210,4 +221,58 @@ describe('testParseComment', function () { expect(res).eq(null); }); + it('parseWASMArraySimpleInfo', function () { + const commentStr = '// Wasmnizer-ts: @WASMArray@'; + const res = parseComment(commentStr); + const isWASMArray = isWASMArrayComment(res); + const packedTypeKind = (res as WASMArray).packedType; + const mutability = (res as WASMArray).mutability; + const nullability = (res as WASMArray).nullability; + + expect(isWASMArray).eq(true); + expect(packedTypeKind).eq(PackedTypeKind.Not_Packed); + expect(mutability).eq(MutabilityKind.Mutable); + expect(nullability).eq(NullabilityKind.Nullable); + }); + it('parseWASMArrayFullInfo', function () { + const commentStr = + '// Wasmnizer-ts: @WASMArray@ '; + const res = parseComment(commentStr); + const isWASMArray = isWASMArrayComment(res); + const packedTypeKind = (res as WASMArray).packedType; + const mutability = (res as WASMArray).mutability; + const nullability = (res as WASMArray).nullability; + + expect(isWASMArray).eq(true); + expect(packedTypeKind).eq(PackedTypeKind.Not_Packed); + expect(mutability).eq(MutabilityKind.Mutable); + expect(nullability).eq(NullabilityKind.Nullable); + }); + it('parseWASMStructSimpleInfo', function () { + const commentStr = '// Wasmnizer-ts: @WASMStruct@ '; + const res = parseComment(commentStr); + const isWASMStruct = isWASMStructComment(res); + + expect(isWASMStruct).eq(true); + }); + it('parseWASMStructFullInfo', function () { + const commentStr = + '// Wasmnizer-ts: @WASMStruct@ <[I8, I16], [Mutable, Immutable], NonNullable, NULL>'; + const res = parseComment(commentStr); + const isWASMStruct = isWASMStructComment(res); + const packedTypeKinds = (res as WASMStruct).packedTypes!; + const mutabilitys = (res as WASMStruct).mutabilitys!; + const nullability = (res as WASMStruct).nullability!; + const baseTypeName = (res as WASMStruct).baseTypeName!; + + expect(isWASMStruct).eq(true); + expect(packedTypeKinds.length).eq(2); + expect(packedTypeKinds[0]).eq(PackedTypeKind.I8); + expect(packedTypeKinds[1]).eq(PackedTypeKind.I16); + expect(mutabilitys.length).eq(2); + expect(mutabilitys[0]).eq(MutabilityKind.Mutable); + expect(mutabilitys[1]).eq(MutabilityKind.Immutable); + expect(nullability).eq(NullabilityKind.NonNullable); + expect(baseTypeName).eq('NULL'); + }); }); diff --git a/tools/validate/wamr/validation.json b/tools/validate/wamr/validation.json index d86f13e7..7a2b900e 100644 --- a/tools/validate/wamr/validation.json +++ b/tools/validate/wamr/validation.json @@ -4183,6 +4183,51 @@ } ] }, + { + "module": "tuple", + "entries": [ + { + "name": "tuple_type_with_constant", + "args": [], + "result": "10\n20.5\nhi" + }, + { + "name": "tuple_type_with_variable", + "args": [], + "result": "[wasm object]\nhi\n90" + }, + { + "name": "tuple_as_param", + "args": [], + "result": "30\nhi" + }, + { + "name": "tuple_as_ret", + "args": [], + "result": "10\n100" + }, + { + "name": "tuple_as_array_elem", + "args": [], + "result": "hi_1\nhi_2\nhi_3" + }, + { + "name": "tuple_as_obj_field", + "args": [], + "result": "hi_1\nhi_2\nhi_3" + }, + { + "name": "tuple_with_array", + "args": [], + "result": "2\nhi_2" + }, + { + "name": "tuple_with_class", + "args": [], + "result": "1\nhi_2" + } + ] + }, { "module": "typealias", "entries": [ @@ -4628,6 +4673,56 @@ } ] }, + { + "module": "wasmType_heapType", + "entries": [ + { + "name": "wasmArrayTypeWithLiteral", + "args": [], + "result": "hi\nhello\n88\n77" + }, + { + "name": "wasmArrayTypeWithNewArray", + "args": [], + "result": "0\n1\n2\n8\n9" + }, + { + "name": "wasmArrayTypeNested", + "args": [], + "result": "0x1:i64" + }, + { + "name": "wasmArrayTypeInArray", + "args": [], + "result": "0x1:i64" + }, + { + "name": "wasmArrayTypeInObj", + "args": [], + "result": "0x1:i64" + }, + { + "name": "wasmStructType", + "args": [], + "result": "10\n99\n20\n33\n2" + }, + { + "name": "wasmStructTypeNested", + "args": [], + "result": "0x1:i64" + }, + { + "name": "wasmStructTypeInArray", + "args": [], + "result": "0x1:i64" + }, + { + "name": "wasmStructTypeInObj", + "args": [], + "result": "0x1:i64" + } + ] + }, { "module": "wasmType_in_otherType", "entries": [