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/package.json b/package.json index 2ca904c8..5940248f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ }, "lint-staged": { "*.ts": [ - "eslint --ignore-path .eslintignore --config .eslintrc.json --fix '**/*.ts'", + "eslint --ignore-path .eslintignore --config .eslintrc.json --fix --quiet '**/*.ts'", "prettier --ignore-path .prettierignore --config .prettierrc.json --write '**/*.ts'" ] }, diff --git a/runtime-library/stdlib/lib_array.c b/runtime-library/stdlib/lib_array.c index 131ed41d..32e86dcb 100644 --- a/runtime-library/stdlib/lib_array.c +++ b/runtime-library/stdlib/lib_array.c @@ -160,7 +160,7 @@ array_concat_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, return NULL; } - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; create_new_array = true; @@ -183,7 +183,7 @@ array_concat_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, fail: if (create_new_array) { - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); } return new_arr_struct; @@ -305,7 +305,7 @@ array_slice_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, return NULL; } - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; for (i = start; i < end; i++) { @@ -326,7 +326,7 @@ array_slice_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, wasm_struct_obj_set_field(new_arr_struct, 1, &tmp_val); end: - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); return new_arr_struct; } @@ -500,7 +500,7 @@ array_splice_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, goto end1; } - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)delete_arr; /* Copy deleted elements to delete_arr*/ @@ -551,7 +551,7 @@ array_splice_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, tmp_val.u32 = delete_count; wasm_struct_obj_set_field(new_arr_struct, 1, &tmp_val); - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); return new_arr_struct; end1: @@ -560,7 +560,7 @@ array_splice_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, return NULL; end2: - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env), "alloc memory failed"); return NULL; @@ -1059,7 +1059,7 @@ array_map_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, void *closure) "alloc memory failed"); return NULL; } - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; /* get current array element type */ @@ -1110,7 +1110,7 @@ array_map_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, void *closure) wasm_struct_obj_set_field(new_arr_struct, 1, &tmp_val); end: - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); return new_arr_struct; } @@ -1190,7 +1190,7 @@ array_filter_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, "alloc memory failed"); goto end1; } - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; for (i = 0; i < new_arr_len; i++) { @@ -1211,7 +1211,7 @@ array_filter_generic(wasm_exec_env_t exec_env, void *ctx, void *obj, wasm_struct_obj_set_field(new_arr_struct, 1, &tmp_val); end2: - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); end1: if (include_refs) { diff --git a/runtime-library/utils/object_utils.c b/runtime-library/utils/object_utils.c index 763e6010..85941d69 100644 --- a/runtime-library/utils/object_utils.c +++ b/runtime-library/utils/object_utils.c @@ -442,7 +442,7 @@ call_wasm_func_with_boxing(wasm_exec_env_t exec_env, dyn_ctx_t ctx, ) { /* unbox_value_from_any will create anyref for any-objects, we must * hold its reference to avoid it being claimed */ - wasm_runtime_push_local_object_ref(exec_env, + wasm_runtime_push_local_obj_ref(exec_env, &local_refs[local_ref_count]); local_refs[local_ref_count++].val = tmp_param.gc_obj; } @@ -454,7 +454,7 @@ call_wasm_func_with_boxing(wasm_exec_env_t exec_env, dyn_ctx_t ctx, } if (local_ref_count) { - wasm_runtime_pop_local_object_refs(exec_env, local_ref_count); + wasm_runtime_pop_local_obj_refs(exec_env, local_ref_count); } is_success = diff --git a/runtime-library/utils/type_utils.c b/runtime-library/utils/type_utils.c index 3f27db54..4624684c 100644 --- a/runtime-library/utils/type_utils.c +++ b/runtime-library/utils/type_utils.c @@ -352,7 +352,7 @@ create_wasm_array_with_string(wasm_exec_env_t exec_env, void **ptr, } /* Push object to local ref to avoid being freed at next allocation */ - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; /* create_wasm_string for every element */ @@ -367,7 +367,7 @@ create_wasm_array_with_string(wasm_exec_env_t exec_env, void **ptr, wasm_struct_obj_new_with_type(exec_env, res_arr_struct_type); if (!new_stringref_array_struct) { - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env), "alloc memory failed"); return NULL; @@ -379,7 +379,7 @@ create_wasm_array_with_string(wasm_exec_env_t exec_env, void **ptr, val.u32 = arrlen; wasm_struct_obj_set_field(new_stringref_array_struct, 1, &val); - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); return new_stringref_array_struct; } #else @@ -420,11 +420,11 @@ create_wasm_array_with_string(wasm_exec_env_t exec_env, void **ptr, /* create new array */ new_arr = wasm_array_obj_new_with_type(exec_env, res_arr_type, arrlen, &init); - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; if (!new_arr) { - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); wasm_runtime_set_exception((wasm_module_inst_t)module_inst, "alloc memory failed"); return NULL; @@ -452,7 +452,7 @@ create_wasm_array_with_string(wasm_exec_env_t exec_env, void **ptr, tmp_val.u32 = arrlen; wasm_struct_obj_set_field(string_array_struct, 1, &tmp_val); - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); return string_array_struct; } #endif /* end of WASM_ENABLE_STRINGREF != 0 */ @@ -672,7 +672,7 @@ create_wasm_string(wasm_exec_env_t exec_env, const char *value) } /* Push object to local ref to avoid being freed at next allocation */ - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_string_struct; val.i32 = 0; @@ -680,7 +680,7 @@ create_wasm_string(wasm_exec_env_t exec_env, const char *value) new_arr = wasm_array_obj_new_with_type(exec_env, string_array_type, len, &val); if (!new_arr) { - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); wasm_runtime_set_exception(module_inst, "alloc memory failed"); return NULL; } @@ -697,7 +697,7 @@ create_wasm_string(wasm_exec_env_t exec_env, const char *value) val.gc_obj = (wasm_obj_t)new_arr; wasm_struct_obj_set_field(new_string_struct, 1, &val); - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); (void)p_end; return new_string_struct; @@ -748,16 +748,16 @@ create_new_array_with_primitive_type(wasm_exec_env_t exec_env, /* create new array */ new_arr = wasm_array_obj_new_with_type(exec_env, res_arr_type, arrlen, &init); - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; if (!new_arr) { - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); wasm_runtime_set_exception((wasm_module_inst_t)module_inst, "alloc memory failed"); return NULL; } - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); return new_arr; } @@ -1025,7 +1025,7 @@ array_to_string(wasm_exec_env_t exec_env, void *ctx, void *obj, void *separator) } /* Push object to local ref to avoid being freed at next allocation */ - wasm_runtime_push_local_object_ref(exec_env, &local_ref); + wasm_runtime_push_local_obj_ref(exec_env, &local_ref); local_ref.val = (wasm_obj_t)new_arr; p = (char *)wasm_array_obj_first_elem_addr(new_arr); @@ -1072,7 +1072,7 @@ array_to_string(wasm_exec_env_t exec_env, void *ctx, void *obj, void *separator) } if (local_ref.val) { - wasm_runtime_pop_local_object_ref(exec_env); + wasm_runtime_pop_local_obj_ref(exec_env); } if (sep) { 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 392e2581..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'; @@ -3233,8 +3234,8 @@ function Array_isArray(module: binaryen.Module) { module.local.get(returnIdx, binaryen.i32), module.i32.const(0), ), - /** 13 is EXArray tag in quickjs */ - module.if(module.i32.eq(is_arr_extref, module.i32.const(13)), setTrue), + /* tag `DynExtRefArray` in runtime library's enum `dyn_type_t` value is 12 */ + module.if(module.i32.eq(is_arr_extref, module.i32.const(12)), setTrue), ); statementArray.push(setDefault); statementArray.push(is_array); @@ -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 2f4d8b75..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, ); } @@ -830,16 +830,26 @@ export namespace FunctionalFuncs { module: binaryen.Module, expression: binaryen.ExpressionRef, expressionType?: binaryen.Type, + isSigned = true, ): binaryen.ExpressionRef { const exprType = expressionType ? expressionType : binaryen.getExpressionType(expression); switch (exprType) { case binaryen.f64: { - return module.i32.trunc_s.f64(expression); + return module.i32.wrap( + convertTypeToI64( + module, + expression, + binaryen.f64, + isSigned, + ), + ); } case binaryen.f32: { - return module.i32.trunc_s.f32(expression); + return isSigned + ? module.i32.trunc_s.f32(expression) + : module.i32.trunc_u.f32(expression); } case binaryen.i64: { return module.i32.wrap(expression); @@ -856,22 +866,29 @@ export namespace FunctionalFuncs { module: binaryen.Module, expression: binaryen.ExpressionRef, expressionType?: binaryen.Type, + isSigned = true, ): binaryen.ExpressionRef { const exprType = expressionType ? expressionType : binaryen.getExpressionType(expression); switch (exprType) { case binaryen.f64: { - return module.i64.trunc_s.f64(expression); + return isSigned + ? module.i64.trunc_s.f64(expression) + : module.i64.trunc_u.f64(expression); } case binaryen.f32: { - return module.i64.trunc_s.f32(expression); + return isSigned + ? module.i64.trunc_s.f32(expression) + : module.i64.trunc_u.f32(expression); } case binaryen.i64: { return expression; } case binaryen.i32: { - return module.i64.extend_s(expression); + return isSigned + ? module.i64.extend_s(expression) + : module.i64.extend_u(expression); } } return binaryen.none; @@ -881,6 +898,7 @@ export namespace FunctionalFuncs { module: binaryen.Module, expression: binaryen.ExpressionRef, expressionType?: binaryen.Type, + isSigned = true, ): binaryen.ExpressionRef { const exprType = expressionType ? expressionType @@ -893,10 +911,14 @@ export namespace FunctionalFuncs { return expression; } case binaryen.i64: { - return module.f32.convert_s.i64(expression); + return isSigned + ? module.f32.convert_s.i64(expression) + : module.f32.convert_u.i64(expression); } case binaryen.i32: { - return module.f32.convert_s.i32(expression); + return isSigned + ? module.f32.convert_s.i32(expression) + : module.f32.convert_u.i32(expression); } } @@ -907,6 +929,7 @@ export namespace FunctionalFuncs { module: binaryen.Module, expression: binaryen.ExpressionRef, expressionType?: binaryen.Type, + isSigned = true, ): binaryen.ExpressionRef { const exprType = expressionType ? expressionType @@ -919,10 +942,14 @@ export namespace FunctionalFuncs { return module.f64.promote(expression); } case binaryen.i64: { - return module.f64.convert_s.i64(expression); + return isSigned + ? module.f64.convert_s.i64(expression) + : module.f64.convert_u.i64(expression); } case binaryen.i32: { - return module.f64.convert_s.i32(expression); + return isSigned + ? module.f64.convert_s.i32(expression) + : module.f64.convert_u.i32(expression); } } return binaryen.none; @@ -1136,24 +1163,8 @@ export namespace FunctionalFuncs { return convertTypeToF64( module, module.i32.shr_s( - convertTypeToI32( - module, - convertTypeToI64( - module, - leftValueRef, - binaryen.f64, - ), - binaryen.i64, - ), - convertTypeToI32( - module, - convertTypeToI64( - module, - rightValueRef, - binaryen.f64, - ), - binaryen.i64, - ), + convertTypeToI32(module, leftValueRef, binaryen.f64), + convertTypeToI32(module, rightValueRef, binaryen.f64), ), binaryen.i32, ); @@ -1162,26 +1173,11 @@ export namespace FunctionalFuncs { return convertTypeToF64( module, module.i32.shr_u( - convertTypeToI32( - module, - convertTypeToI64( - module, - leftValueRef, - binaryen.f64, - ), - binaryen.i64, - ), - convertTypeToI32( - module, - convertTypeToI64( - module, - rightValueRef, - binaryen.f64, - ), - binaryen.i64, - ), + convertTypeToI32(module, leftValueRef, binaryen.f64), + convertTypeToI32(module, rightValueRef, binaryen.f64), ), binaryen.i32, + false, ); } case ts.SyntaxKind.LessThanToken: { @@ -1194,24 +1190,8 @@ export namespace FunctionalFuncs { return convertTypeToF64( module, module.i32.shl( - convertTypeToI32( - module, - convertTypeToI64( - module, - leftValueRef, - binaryen.f64, - ), - binaryen.i64, - ), - convertTypeToI32( - module, - convertTypeToI64( - module, - rightValueRef, - binaryen.f64, - ), - binaryen.i64, - ), + convertTypeToI32(module, leftValueRef, binaryen.f64), + convertTypeToI32(module, rightValueRef, binaryen.f64), ), binaryen.i32, ); @@ -1244,24 +1224,8 @@ export namespace FunctionalFuncs { return convertTypeToF64( module, module.i32.and( - convertTypeToI32( - module, - convertTypeToI64( - module, - leftValueRef, - binaryen.f64, - ), - binaryen.i64, - ), - convertTypeToI32( - module, - convertTypeToI64( - module, - rightValueRef, - binaryen.f64, - ), - binaryen.i64, - ), + convertTypeToI32(module, leftValueRef, binaryen.f64), + convertTypeToI32(module, rightValueRef, binaryen.f64), ), binaryen.i32, ); @@ -1270,24 +1234,8 @@ export namespace FunctionalFuncs { return convertTypeToF64( module, module.i32.or( - convertTypeToI32( - module, - convertTypeToI64( - module, - leftValueRef, - binaryen.f64, - ), - binaryen.i64, - ), - convertTypeToI32( - module, - convertTypeToI64( - module, - rightValueRef, - binaryen.f64, - ), - binaryen.i64, - ), + convertTypeToI32(module, leftValueRef, binaryen.f64), + convertTypeToI32(module, rightValueRef, binaryen.f64), ), binaryen.i32, ); @@ -1305,24 +1253,8 @@ export namespace FunctionalFuncs { return convertTypeToF64( module, module.i32.xor( - convertTypeToI32( - module, - convertTypeToI64( - module, - leftValueRef, - binaryen.f64, - ), - binaryen.i64, - ), - convertTypeToI32( - module, - convertTypeToI64( - module, - rightValueRef, - binaryen.f64, - ), - binaryen.i64, - ), + convertTypeToI32(module, leftValueRef, binaryen.f64), + convertTypeToI32(module, rightValueRef, binaryen.f64), ), ); } @@ -1676,10 +1608,24 @@ export namespace FunctionalFuncs { return module.i64.ge_s(leftValueRef, rightValueRef); } case ts.SyntaxKind.GreaterThanGreaterThanToken: { - return module.i64.shr_s(leftValueRef, rightValueRef); + return convertTypeToI64( + module, + module.i32.shr_s( + convertTypeToI32(module, leftValueRef, binaryen.i64), + convertTypeToI32(module, rightValueRef, binaryen.i64), + ), + binaryen.i32, + ); } case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: { - return module.i64.shr_u(leftValueRef, rightValueRef); + return convertTypeToI64( + module, + module.i32.shr_u( + convertTypeToI32(module, leftValueRef, binaryen.i64), + convertTypeToI32(module, rightValueRef, binaryen.i64), + ), + binaryen.i32, + ); } case ts.SyntaxKind.LessThanToken: { return module.i64.lt_s(leftValueRef, rightValueRef); @@ -1688,19 +1634,47 @@ export namespace FunctionalFuncs { return module.i64.le_s(leftValueRef, rightValueRef); } case ts.SyntaxKind.LessThanLessThanToken: { - return module.i64.shl(leftValueRef, rightValueRef); + return convertTypeToI64( + module, + module.i32.shl( + convertTypeToI32(module, leftValueRef, binaryen.i64), + convertTypeToI32(module, rightValueRef, binaryen.i64), + ), + binaryen.i32, + ); } case ts.SyntaxKind.AmpersandToken: { - return module.i64.and(leftValueRef, rightValueRef); + return convertTypeToI64( + module, + module.i32.and( + convertTypeToI32(module, leftValueRef, binaryen.i64), + convertTypeToI32(module, rightValueRef, binaryen.i64), + ), + binaryen.i32, + ); } case ts.SyntaxKind.BarToken: { - return module.i64.or(leftValueRef, rightValueRef); + return convertTypeToI64( + module, + module.i32.or( + convertTypeToI32(module, leftValueRef, binaryen.i64), + convertTypeToI32(module, rightValueRef, binaryen.i64), + ), + binaryen.i32, + ); } case ts.SyntaxKind.PercentToken: { return module.i64.rem_s(leftValueRef, rightValueRef); } case ts.SyntaxKind.CaretToken: { - return module.i64.xor(leftValueRef, rightValueRef); + return convertTypeToI64( + module, + module.i32.xor( + convertTypeToI32(module, leftValueRef, binaryen.i64), + convertTypeToI32(module, rightValueRef, binaryen.i64), + ), + binaryen.i32, + ); } default: throw new UnimplementError( @@ -2232,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!; @@ -2273,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, ); } @@ -2440,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 1e452563..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}`, + ); } } @@ -1739,18 +1755,21 @@ export class WASMExpressionGen { this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.WASM_I64) { return FunctionalFuncs.convertTypeToI64( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.WASM_F32) { return FunctionalFuncs.convertTypeToF32( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } } else if (fromType.kind === ValueTypeKind.NUMBER) { @@ -1759,18 +1778,21 @@ export class WASMExpressionGen { this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.WASM_I64) { return FunctionalFuncs.convertTypeToI64( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.WASM_F32) { return FunctionalFuncs.convertTypeToF32( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } } else if (fromType.kind === ValueTypeKind.WASM_I64) { @@ -1785,12 +1807,14 @@ export class WASMExpressionGen { this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.WASM_F32) { return FunctionalFuncs.convertTypeToF32( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } } else if (fromType.kind === ValueTypeKind.WASM_F32) { @@ -1799,18 +1823,21 @@ export class WASMExpressionGen { this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.NUMBER) { return FunctionalFuncs.convertTypeToF64( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } else if (toType.kind === ValueTypeKind.WASM_I64) { return FunctionalFuncs.convertTypeToI64( this.module, fromValueRef, fromTypeRef, + value.isSigned, ); } } else if (fromType.kind === ValueTypeKind.BOOLEAN) { @@ -3022,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( @@ -3652,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}`); @@ -3672,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}`); } @@ -3718,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; @@ -3746,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), @@ -3768,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) { @@ -3822,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 @@ -3908,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}`); } @@ -3917,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, @@ -3926,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: { @@ -3975,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}`); } @@ -4360,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; @@ -4372,7 +4527,7 @@ export class WASMExpressionGen { forLoopIdx.index, forLoopIdx.type, ), - arrayOriHeapType, + arrayOriTypeRef, false, ); // box the element by dyntype_new_xxx @@ -4555,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 ( @@ -4575,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, @@ -4676,7 +4842,7 @@ export class WASMExpressionGen { forLoopIdx.index, forLoopIdx.type, ), - arrayOriHeapType, + arrayOriTypeRef, false, ), ); @@ -4836,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/expression.ts b/src/expression.ts index ae13f7fa..792016f2 100644 --- a/src/expression.ts +++ b/src/expression.ts @@ -19,8 +19,8 @@ import { isTypeGeneric, processEscape, processGenericType, - getTypeArgumentsFromParameters, - genericMethodSpecialization, + calculateTypeArguments, + methodSpecialize, } from './utils.js'; import { TSFunction, @@ -707,7 +707,7 @@ export default class ExpressionProcessor { // paramter type const formalParameters = originalFuncType.getParamTypes(); - typeArguments = getTypeArgumentsFromParameters( + typeArguments = calculateTypeArguments( formalParameters, typeParameters, argTypes, @@ -718,7 +718,11 @@ export default class ExpressionProcessor { const typeNames = new Array(); typeArguments.forEach((v) => { if (v.kind !== TypeKind.TYPE_PARAMETER) { - typeNames.push(`${v.kind}`); + if (v instanceof TSClass) { + typeNames.push(v.className); + } else { + typeNames.push(`${v.kind}`); + } } }); const typeSignature = @@ -814,7 +818,7 @@ export default class ExpressionProcessor { if (!res.method) { const origType = classType.getMethod(methodName); - genericMethodSpecialization( + methodSpecialize( origType.method!.type, typeArguments, this.parserCtx, @@ -989,12 +993,11 @@ export default class ExpressionProcessor { // paramter type const formalParameters = genericClassType.ctorType.getParamTypes(); - typeArguments = - getTypeArgumentsFromParameters( - formalParameters, - typeParameters, - argTypes, - ); + typeArguments = calculateTypeArguments( + formalParameters, + typeParameters, + argTypes, + ); } else if (newExpr.typeArguments) { typeArguments = newExpr.typeArguments; } @@ -1265,10 +1268,20 @@ export default class ExpressionProcessor { if ( typeArguments[index].kind !== TypeKind.TYPE_PARAMETER - ) - typeArgumentsSignature.push( - `${typeArguments[index].kind}`, - ); + ) { + if ( + typeArguments[index] instanceof TSClass + ) { + typeArgumentsSignature.push( + (typeArguments[index] as TSClass) + .className, + ); + } else { + typeArgumentsSignature.push( + `${typeArguments[index].kind}`, + ); + } + } }); } } diff --git a/src/scope.ts b/src/scope.ts index 4a788502..0b710e66 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -13,6 +13,7 @@ import { FunctionKind, getMethodPrefix, TSContext, + builtinWasmTypes, } from './type.js'; import { ParserContext } from './frontend.js'; import { @@ -22,7 +23,7 @@ import { Import, Export, parseComment, - parseCommentBasedNode, + parseCommentBasedFuncNode, } from './utils.js'; import { Parameter, Variable } from './variable.js'; import { Statement } from './statement.js'; @@ -351,7 +352,12 @@ export class Scope { } public findType(typeName: string, nested = true): Type | undefined { - const res = builtinTypes.get(typeName); + let res = builtinTypes.get(typeName); + if (res) { + return res; + } + + res = builtinWasmTypes.get(typeName); if (res) { return res; } @@ -857,7 +863,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); @@ -1214,7 +1220,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 d24d8f89..d1d1b84c 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( @@ -939,36 +962,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; @@ -978,39 +1015,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 { @@ -1065,6 +1117,48 @@ function wrapObjToAny(value: SemanticsValue, type: ValueType) { return new CastValue(SemanticsValueKind.OBJECT_CAST_ANY, type, value); } +function checkSigned(tmpValue: BinaryExprValue): boolean { + if ( + tmpValue.opKind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken + ) { + return false; + } + if (tmpValue.left instanceof BinaryExprValue) { + tmpValue = tmpValue.left; + return checkSigned(tmpValue); + } + if (tmpValue.right instanceof BinaryExprValue) { + tmpValue = tmpValue.right; + return checkSigned(tmpValue); + } + return true; +} + +function judgeIsInt(value: number) { + if (value >= -2147483648 && value < 2147483647 && value % 1 === 0) { + return true; + } else { + return false; + } +} + +function judgeIsF32(value: number) { + if (value >= -8388607 && value < 8388607) { + return true; + } else { + return false; + } +} + +function checkOverflow(value: LiteralValue, type: ValueType) { + if (type.kind === ValueTypeKind.INT) { + return judgeIsInt(value.value as number); + } else if (type.kind === ValueTypeKind.WASM_F32) { + return judgeIsF32(value.value as number); + } + return true; +} + export function newCastValue( type: ValueType, value: SemanticsValue, @@ -1239,16 +1333,24 @@ export function newCastValue( ) if ( value instanceof LiteralValue && - value.type.kind === ValueTypeKind.NUMBER + value.type.kind === ValueTypeKind.NUMBER && + checkOverflow(value, type) ) { value.type = type; return value; } else { - return new CastValue( + let isSigned = true; + const tmpValue = value; + if (tmpValue instanceof BinaryExprValue) { + isSigned = checkSigned(tmpValue); + } + const castedValue = new CastValue( SemanticsValueKind.VALUE_CAST_VALUE, type, value, ); + castedValue.isSigned = isSigned; + return castedValue; } if (value_type.kind == ValueTypeKind.ANY) return new CastValue( @@ -1433,10 +1535,9 @@ function typeUp(upValue: SemanticsValue, downValue: SemanticsValue): boolean { } if ( downValue instanceof LiteralValue && - typeof downValue.value === 'number' && - (downValue.value as number) % 1 === 0 + typeof downValue.value === 'number' ) { - return true; + return judgeIsInt(downValue.value as number); } // TODO: check if upValue's value is integer, if true, return true } @@ -1697,6 +1798,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)) { @@ -1745,7 +1851,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 ); } @@ -2340,19 +2448,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, ); @@ -2420,7 +2535,7 @@ function buildNewClass( function buildNewArrayParameters( context: BuildContext, - arr_type: ArrayType, + arr_type: ArrayType | WASMArrayType, params: Expression[] | undefined, valueTypeArgs: ValueType[] | undefined, ): SemanticsValue { @@ -2434,8 +2549,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); @@ -2443,22 +2565,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; } @@ -2567,6 +2699,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 b2a43aa7..2cbdcd81 100644 --- a/src/semantics/type_creator.ts +++ b/src/semantics/type_creator.ts @@ -15,6 +15,9 @@ import { TSEnum, TSContext, TSTypeWithArguments, + WasmArrayType, + TSTuple, + WasmStructType, } from '../type.js'; import { InternalNames } from './internal.js'; @@ -45,6 +48,9 @@ import { ObjectTypeFlag, ClosureContextType, ValueTypeWithArguments, + WASMArrayType, + TupleType, + WASMStructType, } from './value_types.js'; import { BuildContext } from './builder_context.js'; @@ -100,6 +106,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(); @@ -389,6 +406,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 @@ -404,6 +434,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 43d4bc41..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( @@ -690,6 +705,8 @@ type CastValueKind = | SemanticsValueKind.ANY_CAST_OBJECT | SemanticsValueKind.ANY_CAST_VALUE; export class CastValue extends SemanticsValue { + isSigned = true; + constructor( kind: CastValueKind, type: ValueType, @@ -808,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 { @@ -1168,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; + } } } @@ -1214,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[]; @@ -1357,7 +1381,7 @@ export function operatorString(kind: ts.BinaryOperator): string { case ts.SyntaxKind.GreaterThanGreaterThanToken: return '>>'; case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - return '<<<'; + return '>>>'; case ts.SyntaxKind.LessThanToken: return '<'; case ts.SyntaxKind.LessThanEqualsToken: diff --git a/src/semantics/value_types.ts b/src/semantics/value_types.ts index 2de32310..a6cb0124 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); @@ -661,3 +735,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 418acf0c..b9fdc7cc 100644 --- a/src/type.ts +++ b/src/type.ts @@ -14,21 +14,24 @@ import { NamespaceScope, Scope, ScopeKind, - importSearchTypes, } from './scope.js'; import { Parameter, Variable } from './variable.js'; import { Expression } from './expression.js'; import { Logger } from './log.js'; import { DefaultTypeId, + MutabilityKind, + NullabilityKind, + PackedTypeKind, processGenericType, isTypeGeneric, createScopeBySpecializedType, - genericClassTransformation, + isWASMArrayComment, + isWASMStructComment, + parseCommentBasedTypeAliasNode, } from './utils.js'; -import { TypeError } from './error.js'; +import { CommentError, TypeError } from './error.js'; import { BuiltinNames } from '../lib/builtin/builtin_name.js'; -import { INSPECT_MAX_BYTES } from 'buffer'; export const enum TypeKind { VOID = 'void', @@ -44,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', @@ -104,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; @@ -116,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(); @@ -820,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; @@ -831,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(); static specializedTypeCache = new Map[]>(); @@ -952,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); @@ -997,13 +1096,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: { @@ -1012,7 +1111,7 @@ export class TypeResolver { node as ts.ClassDeclaration, classType as TSClass, ); - this.addTypeToTypeMap(classType, node); + this.addTypeToScopeTypeMap(classType, node); break; } case ts.SyntaxKind.InterfaceDeclaration: { @@ -1021,46 +1120,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; } @@ -1068,7 +1227,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: @@ -1111,7 +1270,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 ( this.currentScope!.kind === ScopeKind.FunctionScope && @@ -1139,37 +1298,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) { @@ -1201,73 +1332,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( @@ -1296,11 +1386,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 : '', @@ -1308,12 +1401,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) || @@ -1326,12 +1423,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; + } + } + } } } } @@ -1339,12 +1448,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'); } @@ -1386,9 +1515,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); @@ -1423,36 +1552,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) @@ -1460,25 +1593,25 @@ 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); }); res = processGenericType( - tsType, + type, typeArguments, - (tsType as TSClass).typeArguments!, + (type as TSClass).typeArguments!, this.parserCtx, ); } 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?? */ @@ -1486,38 +1619,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( @@ -1617,20 +1787,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 { @@ -1755,7 +1943,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); // if this parameter type is generic, may need to deal with the generic chain if (isTypeGeneric(customType)) { //function and class @@ -1789,22 +1977,13 @@ export class TypeResolver { //TODO } } - - /* 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; @@ -2165,7 +2344,7 @@ export class TypeResolver { // class Generic {...}; // class GenericBase1 extends Generic {...}; // class GenericBase2 extends Generic {...}; - baseClassType = genericClassTransformation( + baseClassType = processGenericType( baseClassType, typeArguments, baseClassType.typeArguments!, @@ -2752,6 +2931,46 @@ export class TypeResolver { } return false; } + + public static findTypeInSpecialiezedTypeCache( + genericType: TSTypeWithArguments, + signature: string, + ) { + const genericOwner = genericType.genericOwner + ? genericType.genericOwner + : genericType; + + const typeSignature = + genericType instanceof TSClass + ? genericType.className + signature + : signature; + const cache = TypeResolver.specializedTypeCache.get(genericOwner); + let found: Type | undefined; + if (cache) { + cache.forEach((v) => { + if (v.has(typeSignature)) { + found = v.get(typeSignature); + } + }); + } + if (found) return found; + } + + public static updateSpecializedTypeCache( + genericType: TSTypeWithArguments, + typeSignature: string, + specializedType: Type, + ) { + if (TypeResolver.specializedTypeCache.has(genericType)) { + const value = new Map(); + value.set(typeSignature, specializedType); + TypeResolver.specializedTypeCache.get(genericType)!.push(value); + } else { + const value = new Map(); + value.set(typeSignature, specializedType); + TypeResolver.specializedTypeCache.set(genericType, [value]); + } + } } export class CustomTypeResolver { diff --git a/src/utils.ts b/src/utils.ts index b01dd4f2..ecb133a7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,7 +22,6 @@ import { BuiltinNames } from '../lib/builtin/builtin_name.js'; import { ParserContext } from './frontend.js'; import { FunctionKind, - getMethodPrefix, Type, TSInterface, TypeKind, @@ -35,6 +34,9 @@ import { builtinWasmTypes, TypeResolver, TSTypeWithArguments, + WasmArrayType, + WasmStructType, + TSTuple, } from './type.js'; import { UnimplementError } from './error.js'; import { Statement } from './statement.js'; @@ -70,6 +72,8 @@ export enum CommentKind { NativeSignature = 'NativeSignature', Import = 'Import', Export = 'Export', + WASMArray = 'WASMArray', + WASMStruct = 'WASMStruct', } export interface NativeSignature { @@ -86,6 +90,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) { @@ -478,694 +513,148 @@ function decimalizationInternal(value: string, systemNumeration: number) { return decimal.toString(); } -export function genericFunctionSpecialization( +export function genericFunctionProcessor( genericFuncType: TSFunction, typeArguments: Type[], typeParameters: TSTypeParameter[], context: ParserContext, + isMethod = false, + doSpecialization = false, ): TSFunction { if ( - !genericFuncType.typeArguments || typeArguments.length == 0 || - typeParameters.length == 0 - ) + typeParameters.length == 0 || + (!isMethod && !genericFuncType.typeArguments) + ) { return genericFuncType; - - const typeArgumentsSignature: Array = []; - const _typeParameters = genericFuncType.typeArguments!; - _typeParameters.forEach((type) => { - const index = typeParameters.findIndex((t) => { - return t.name === type.name; - }); - if (index == -1) { - throw new UnimplementError( - `${type.name} not found in typeParameters`, - ); - } - typeArgumentsSignature.push(`${typeArguments[index].kind}`); - }); + } const genericOwner = genericFuncType.genericOwner ? genericFuncType.genericOwner : genericFuncType; - const typeSignature = - typeArgumentsSignature.length > 0 - ? '<' + typeArgumentsSignature.join(',') + '>' - : ''; - const cache = TypeResolver.specializedTypeCache.get(genericOwner); - let found: Type | undefined; - if (cache) { - cache.forEach((v) => { - if (v.has(typeSignature)) { - found = v.get(typeSignature); - } - }); - } - if (found) return found as TSFunction; - - const funcType = genericFuncType as TSFunction; - const newFuncType = funcType.clone(); - // specialized function does not have typeArguments - newFuncType.setTypeParameters(undefined); - // specialized function type need to reset belongedClass and belongedScope - newFuncType.belongedClass = undefined; - newFuncType.setBelongedScope(undefined); - // regenerate the parameter list - newFuncType.setParamTypes([]); - funcType.getParamTypes().forEach((paramType) => { - const newParamType = genericTypeSpecialization( - paramType, - typeArguments, + let typeSignature = ''; + if (doSpecialization && !isMethod) { + const _typeParameters = genericFuncType.typeArguments!; + typeSignature = getTypeSignature( + _typeParameters, typeParameters, - context, - ); - newFuncType.addParamType(newParamType); - }); - newFuncType.setGenericOwner(genericOwner); - - // update specializedTypeCache - newFuncType.setSpecializedArguments(typeArguments); - if (TypeResolver.specializedTypeCache.has(genericOwner)) { - const value = new Map(); - value.set(typeSignature, newFuncType); - TypeResolver.specializedTypeCache.get(genericOwner)!.push(value); - } else { - const value = new Map(); - value.set(typeSignature, newFuncType); - TypeResolver.specializedTypeCache.set(genericOwner, [value]); - } - - newFuncType.returnType = genericTypeSpecialization( - funcType.returnType, - typeArguments, - typeParameters, - context, - ); - return newFuncType; -} - -export function genericClassMethodSpecialization( - genericMethodType: TSFunction, - typeArguments: Type[], - typeParameters: TSTypeParameter[], - context: ParserContext, -): TSFunction { - if (typeArguments.length == 0 || typeParameters.length == 0) - return genericMethodType; - - const typeArgumentsSignature: Array = []; - const _typeParameters = genericMethodType.typeArguments - ? genericMethodType.typeArguments - : genericMethodType.belongedClass!.typeArguments!; - _typeParameters.forEach((type) => { - const index = typeParameters.findIndex((t) => { - return t.name === type.name; - }); - if (index == -1) { - throw new UnimplementError( - `${type.name} not found in typeParameters`, - ); - } - typeArgumentsSignature.push(`${typeArguments[index].kind}`); - }); - - const genericOwner = genericMethodType.genericOwner - ? genericMethodType.genericOwner - : genericMethodType; - const newMethodType = genericMethodType.clone(); - // specialized function does not have typeArguments - newMethodType.setTypeParameters(undefined); - // specialized function type need to reset belongedClass and belongedScope - newMethodType.belongedClass = undefined; - newMethodType.setBelongedScope(undefined); - - // regenerate the parameter list - newMethodType.setParamTypes([]); - genericMethodType.getParamTypes().forEach((paramType) => { - const newParamType = genericTypeSpecialization( - paramType, typeArguments, - typeParameters, - context, ); - newMethodType.addParamType(newParamType); - }); - newMethodType.setGenericOwner(genericOwner); - - // update specializedTypeCache - if (genericMethodType.typeArguments) { - newMethodType.setSpecializedArguments(typeArguments); - } - - // prevent infinite recursive call - if ( - genericMethodType.funcKind == FunctionKind.CONSTRUCTOR || - (genericMethodType.returnType instanceof TSClass && - genericMethodType.returnType.toString === - genericMethodType.belongedClass!.toString) - ) - return newMethodType; - - newMethodType.returnType = genericTypeSpecialization( - genericMethodType.returnType, - typeArguments, - typeParameters, - context, - ); - return newMethodType; -} - -/** - * class A { - * a: number; - * echo(param: T) {...}; - * } - * const a = new A(); - * this class type does not contain 'typeParameters'. - */ -export function genericMethodSpecialization( - genericMethodType: TSFunction, - typeArguments: Type[], - context: ParserContext, -) { - const classType = genericMethodType.belongedClass; - const originalFunctionScope = genericMethodType.belongedScope; - const typeParameters = genericMethodType.typeArguments; - - // insufficient information used for specialization - if (!classType || !typeParameters || !originalFunctionScope) return; - - const typeNames = new Array(); - typeArguments.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - const typeSignature = '<' + typeNames.join(',') + '>'; - const newFuncName = originalFunctionScope.getName() + typeSignature; - const specializedFunctionType = genericClassMethodSpecialization( - genericMethodType, - typeArguments, - typeParameters, - context, - ) as TSFunction; - specializedFunctionType.belongedClass = classType; - - // create new function scope begin - const newFuncScope = new FunctionScope(originalFunctionScope.parent!); - originalFunctionScope.specialize(newFuncScope); - newFuncScope.setClassName(classType.className); - newFuncScope.setFuncName(newFuncName); - newFuncScope.setFuncType(specializedFunctionType); - specializedFunctionType.setBelongedScope(newFuncScope); - if (originalFunctionScope.mangledName !== '') { - const genericMangledName = originalFunctionScope.mangledName; - const reverse = genericMangledName.split('|').reverse(); - // class name - reverse[0] = newFuncName; - newFuncScope.mangledName = reverse.reverse().join('|'); - } - newFuncScope.setGenericOwner(originalFunctionScope); - originalFunctionScope.addSpecializedScope(typeSignature, newFuncScope); - const optional = classType.getMethod(originalFunctionScope.getName()) - .method!.optional; - classType.addMethod({ - name: newFuncName, - type: specializedFunctionType, - optional: optional, - }); - classType.overrideOrOwnMethods.add(newFuncName); - - // specialize a generic member function on the inheritance chain - const drivedClasses = classType.getDrivedClasses(); - if (!drivedClasses) return; - drivedClasses.forEach((c) => { - if (c.memberFuncs.length > 0) { - c.memberFuncs.forEach((m) => { - genericMethodSpecialization(m.type, typeArguments, context); - }); - } - }); -} - -export function genericClassSpecialization( - genericClassType: TSClass, - typeArguments: Type[], - typeParameters: TSTypeParameter[], - context: ParserContext, -): TSClass { - if (typeArguments.length == 0 || typeParameters.length == 0) - return genericClassType; - - const typeArgumentsSignature: Array = []; - const _typeParameters = genericClassType.typeArguments; - if (_typeParameters) { - _typeParameters.forEach((type) => { - const index = typeParameters.findIndex((t) => { - return t.name === type.name; - }); - if (index == -1) { - throw new UnimplementError( - `${type.name} not found in typeParameters`, - ); - } - typeArgumentsSignature.push(`${typeArguments[index].kind}`); - }); - } - - const genericOwner = genericClassType.genericOwner - ? genericClassType.genericOwner - : genericClassType; - const typeSignature = - typeArgumentsSignature.length > 0 - ? '<' + typeArgumentsSignature.join(',') + '>' - : ''; - const cache = TypeResolver.specializedTypeCache.get(genericOwner); - let found: Type | undefined; - if (cache) { - cache.forEach((v) => { - if (v.has(genericClassType.className + typeSignature)) { - found = v.get(genericClassType.className + typeSignature); - } - }); + const found = TypeResolver.findTypeInSpecialiezedTypeCache( + genericFuncType, + typeSignature, + ); + if (found) return found as TSFunction; } - if (found) return found as TSClass; - - /** - * class Base { - * x: T; - * action(param: T) {....} - * } - */ - if (genericClassType.typeArguments) { - let newClassType: TSClass; - if (genericClassType.kind === TypeKind.CLASS) { - newClassType = new TSClass(); - } else { - newClassType = new TSInterface(); - } - const specializedName = genericClassType.className + typeSignature; - newClassType.setClassName(specializedName); - if (genericClassType.mangledName !== '') { - const genericMangledName = genericClassType.mangledName; - const reverse = genericMangledName.split('|').reverse(); - reverse[0] = specializedName; - newClassType.mangledName = reverse.reverse().join('|'); - } - newClassType.setGenericOwner(genericOwner as TSTypeWithArguments); - newClassType.setSpecializedArguments(typeArguments); - newClassType.hasDeclareCtor = genericClassType.hasDeclareCtor; - newClassType.isDeclare = genericClassType.isDeclare; - newClassType.isLiteral = genericClassType.isLiteral; - newClassType.overrideOrOwnMethods = - genericClassType.overrideOrOwnMethods; - newClassType.traverseStatus = genericClassType.traverseStatus; - // update specializedTypeCache - if (TypeResolver.specializedTypeCache.has(genericOwner)) { - const value = new Map(); - value.set(specializedName, newClassType); - TypeResolver.specializedTypeCache.get(genericOwner)!.push(value); - } else { - const value = new Map(); - value.set(specializedName, newClassType); - TypeResolver.specializedTypeCache.set(genericOwner, [value]); - } + const newFuncType = genericFuncType.clone(); + newFuncType.setGenericOwner(genericOwner); + newFuncType.setBelongedScope(undefined); - // base class type - if (genericClassType.getBase()) { - let baseType = genericClassType.getBase(); - if (baseType) { - if (isTypeGeneric(baseType)) { - baseType = genericTypeSpecialization( - baseType, - typeArguments, - typeParameters, - context, - ) as TSClass; - } - newClassType.setBase(baseType); - } + if (doSpecialization) { + if (!isMethod || (isMethod && genericFuncType.typeArguments)) { + newFuncType.setSpecializedArguments(typeArguments); } + // specialized function does not have typeArguments + newFuncType.setTypeParameters(undefined); - const implInfc = genericClassType.getImplInfc(); - if (implInfc && isTypeGeneric(implInfc)) { - const newInfcType = genericTypeSpecialization( - implInfc, - typeArguments, - typeParameters, - context, - ) as TSInterface; - newClassType.setImplInfc(newInfcType); - } else { - newClassType.setImplInfc(implInfc); + // iff method + if (newFuncType.belongedClass) { + newFuncType.belongedClass = undefined; } - - // specialized member variables - genericClassType.fields.forEach((field) => { - const newFieldType = genericTypeSpecialization( - field.type, - typeArguments, - typeParameters, - context, - ); - newClassType.addMemberField({ - name: field.name, - type: newFieldType, - }); - }); - // specialized member functions - genericClassType.memberFuncs.forEach((func) => { - const funcName = func.name; - const funcKind = func.type.funcKind; - let realFuncName = funcName; - if (funcKind == FunctionKind.GETTER) { - realFuncName = 'get_' + funcName; - } else if (funcKind == FunctionKind.SETTER) { - realFuncName = 'set_' + funcName; - } - const isOwnMethod = - newClassType.overrideOrOwnMethods.has(realFuncName); - if (isOwnMethod) { - const newFuncType = genericClassMethodSpecialization( - func.type, - typeArguments, - typeParameters, - context, - ) as TSFunction; - newFuncType.belongedClass = newClassType; - newClassType.addMethod({ - name: funcName, - type: newFuncType, - }); - } else { - let base = newClassType.getBase(); - let found = func; - while (base) { - const isOwnMethod = - base.overrideOrOwnMethods.has(realFuncName); - if (isOwnMethod) { - base.memberFuncs.forEach((f) => { - if ( - f.name == funcName && - f.type.funcKind == funcKind - ) { - found = f; - return; - } - }); - break; - } - base = base.getBase(); - } - newClassType.addMethod(found); - } - }); - genericClassType.staticFields.forEach((field) => { - const newStaticFieldType = genericTypeSpecialization( - field.type, - typeArguments, - typeParameters, - context, - ); - if (newStaticFieldType instanceof TSTypeWithArguments) - newClassType.addStaticMemberField({ - name: field.name, - type: newStaticFieldType, - }); - }); - // specialized constructor - if (genericClassType.ctorType) { - const newCtor = genericClassMethodSpecialization( - genericClassType.ctorType, - typeArguments, - typeParameters, - context, - ) as TSFunction; - newCtor.belongedClass = newClassType; - newClassType.ctorType = newCtor; - newClassType.ctorType.returnType = newClassType; - } - - if (genericClassType.belongedScope) - genericClassType.belongedScope.parent?.addType( - newClassType.className, - newClassType, - ); - return newClassType; } else { - /** - * class Base { - * x: number; - * action(param: T) {....} - * } - */ - genericClassType.memberFuncs.forEach((func) => { - if (isTypeGeneric(func.type)) { - const genericFuncType = func.type; - const genericFunctionScope = genericFuncType.belongedScope!; - // generate the name of the specialized function name - const typeNames = new Array(); - typeArguments.forEach((v) => { - if (v.kind !== TypeKind.TYPE_PARAMETER) - typeNames.push(`${v.kind}`); - }); - const typeSignature = - typeNames.length > 0 ? '<' + typeNames.join(',') + '>' : ''; - const genericFunctionScopeName = genericFunctionScope.getName(); - const specializedFunctionScopeName = - genericFunctionScopeName + typeSignature; - // whether the specialized generic method already exists - const existMethod = genericClassType.getMethod( - specializedFunctionScopeName, - ); - if (existMethod.method) return genericClassType; - - genericMethodSpecialization( - genericFuncType, - typeArguments, - context, - ); - } - }); - return genericClassType; - } -} - -/** - * @describe specialize a generic type - * @param genericType the generic type that need to be specialized - * @param typeArguments specialized type arguments list - * @param typeParameters generic parameter type list - * @returns a new specialized type,if the same specialization is executed for the second time, - * generictype is returned to prevent the scope from being created again. - */ -export function genericTypeSpecialization( - genericType: Type, - typeArguments: Type[], - typeParameters: TSTypeParameter[], - context: ParserContext, -): Type { - // the type that need to be specialized must be generic - if (!isTypeGeneric(genericType)) return genericType; - - switch (genericType.kind) { - case TypeKind.VOID: - case TypeKind.BOOLEAN: - case TypeKind.NUMBER: - case TypeKind.ANY: - case TypeKind.UNDEFINED: - case TypeKind.STRING: - case TypeKind.UNKNOWN: - case TypeKind.NULL: - case TypeKind.WASM_I32: - case TypeKind.WASM_I64: - case TypeKind.WASM_F32: - case TypeKind.WASM_F64: - case TypeKind.WASM_ANYREF: { - return genericType; - } - case TypeKind.ARRAY: { - return new TSArray( - genericTypeSpecialization( - (genericType as TSArray).elementType, - typeArguments, - typeParameters, - context, - ), - ); - } - case TypeKind.UNION: { - const unionType = genericType as TSUnion; - const newUnion = new TSUnion(); - unionType.types.forEach((t) => { - if (t.kind == TypeKind.UNDEFINED) { - newUnion.addType(t); - } else { - const newType = genericTypeSpecialization( + // regenerate typeParameters + if (!isMethod) { + newFuncType.setSpecializedArguments(typeArguments); + newFuncType.setTypeParameters([]); + genericFuncType.typeArguments!.forEach((t) => { + newFuncType.addTypeParameter( + genericCoreProcessor( t, typeArguments, typeParameters, context, - ); - newUnion.addType(newType); - } + ) as TSTypeParameter, + ); }); - return newUnion; - } - case TypeKind.FUNCTION: { - const newFuncType = genericFunctionSpecialization( - genericType as TSFunction, - typeArguments, - typeParameters, - context, - ); - return newFuncType; - } - case TypeKind.CLASS: - case TypeKind.INTERFACE: { - const newClassType = genericClassSpecialization( - genericType as TSClass, - typeArguments, - typeParameters, - context, - ); - return newClassType; - } - case TypeKind.TYPE_PARAMETER: { - const gType = genericType as TSTypeParameter; - if (typeArguments && typeParameters) { - for (let i = 0; i < typeParameters.length; i++) { - if (typeParameters[i].name === gType.name) { - return typeArguments[i]; - } - } - } - return builtinTypes.get('any')!; - } - default: { - throw new UnimplementError( - `Not implemented type: ${genericType.kind}`, - ); } } -} -export function genericFuncTransformation( - genericFuncType: TSFunction, - newTypeParameters: Type[], - typeParameters: TSTypeParameter[], - context: ParserContext, -): TSFunction { - if ( - !genericFuncType.typeArguments || - newTypeParameters.length == 0 || - typeParameters.length == 0 - ) - return genericFuncType; - - const genericOwner = genericFuncType.genericOwner - ? genericFuncType.genericOwner - : genericFuncType; - const newFuncType = genericFuncType.clone(); - newFuncType.setBelongedScope(undefined); - newFuncType.setGenericOwner(genericOwner); - newFuncType.setSpecializedArguments(newTypeParameters); - - // regenerate typeParameters - newFuncType.setTypeParameters([]); - genericFuncType.typeArguments.forEach((t) => { - newFuncType.addTypeParameter( - genericTypeTransformation( - t, - newTypeParameters, - typeParameters, - context, - ) as TSTypeParameter, - ); - }); - // regenerate the parameter list + // regenerate the parameters newFuncType.setParamTypes([]); genericFuncType.getParamTypes().forEach((paramType) => { - const newParamType = genericTypeTransformation( + const newParamType = genericCoreProcessor( paramType, - newTypeParameters, + typeArguments, typeParameters, context, + true, ); newFuncType.addParamType(newParamType); }); - newFuncType.returnType = genericTypeTransformation( - genericFuncType.returnType, - newTypeParameters, - typeParameters, - context, - ); - return newFuncType; -} - -export function genericMethodTransformation( - genericMethodType: TSFunction, - newTypeParameters: Type[], - typeParameters: TSTypeParameter[], - context: ParserContext, -): TSFunction { - if (newTypeParameters.length == 0 || typeParameters.length == 0) - return genericMethodType; - - const genericOwner = genericMethodType.genericOwner - ? genericMethodType.genericOwner - : genericMethodType; - const newMethodType = genericMethodType.clone(); - newMethodType.setBelongedScope(undefined); - newMethodType.setGenericOwner(genericOwner); - - // regenerate the parameter list - newMethodType.setParamTypes([]); - genericMethodType.getParamTypes().forEach((paramType) => { - const newParamType = genericTypeTransformation( - paramType, - newTypeParameters, - typeParameters, - context, - ); - newMethodType.addParamType(newParamType); - }); // prevent infinite recursive call if ( - genericMethodType.funcKind == FunctionKind.CONSTRUCTOR || - (genericMethodType.returnType instanceof TSClass && - genericMethodType.returnType.toString === - genericMethodType.belongedClass!.toString) + isMethod && + (genericFuncType.funcKind == FunctionKind.CONSTRUCTOR || + (genericFuncType.returnType instanceof TSClass && + genericFuncType.returnType.toString === + genericFuncType.belongedClass!.toString)) ) - return newMethodType; + return newFuncType; + + // update specializedTypeCache + if (doSpecialization && !isMethod) { + TypeResolver.updateSpecializedTypeCache( + genericOwner, + typeSignature, + newFuncType, + ); + } - newMethodType.returnType = genericTypeTransformation( - genericMethodType.returnType, - newTypeParameters, + newFuncType.returnType = genericCoreProcessor( + genericFuncType.returnType, + typeArguments, typeParameters, context, + true, ); - return newMethodType; + return newFuncType; } -export function genericClassTransformation( +export function genericClassProcessor( genericClassType: TSClass, - newTypeParameters: Type[], + typeArguments: Type[], typeParameters: TSTypeParameter[], context: ParserContext, + doSpecialization = false, namePrefix?: string, ): TSClass { if ( - !genericClassType.typeArguments || - newTypeParameters.length == 0 || - typeParameters.length == 0 + typeArguments.length == 0 || + typeParameters.length == 0 || + (!doSpecialization && !genericClassType.typeArguments) ) return genericClassType; - const genericOwner = genericClassType.genericOwner - ? genericClassType.genericOwner - : genericClassType; + let typeSignature = ''; + if (doSpecialization) { + const _typeParameters = genericClassType.typeArguments; + if (_typeParameters) { + typeSignature = getTypeSignature( + _typeParameters, + typeParameters, + typeArguments, + ); + } + const found = TypeResolver.findTypeInSpecialiezedTypeCache( + genericClassType, + typeSignature, + ); + if (found) return found as TSClass; + } + if (genericClassType.typeArguments) { let newClassType: TSClass; if (genericClassType.kind === TypeKind.CLASS) { @@ -1173,43 +662,71 @@ export function genericClassTransformation( } else { newClassType = new TSInterface(); } + newClassType.hasDeclareCtor = genericClassType.hasDeclareCtor; newClassType.isDeclare = genericClassType.isDeclare; newClassType.isLiteral = genericClassType.isLiteral; newClassType.overrideOrOwnMethods = genericClassType.overrideOrOwnMethods; newClassType.traverseStatus = genericClassType.traverseStatus; - const newClassName = namePrefix + + const newClassName = doSpecialization + ? genericClassType.className + typeSignature + : namePrefix ? namePrefix + '_' + genericClassType.className : genericClassType.className; newClassType.setClassName(newClassName); + + const genericOwner = genericClassType.genericOwner + ? genericClassType.genericOwner + : genericClassType; newClassType.setGenericOwner(genericOwner as TSClass); - newClassType.setSpecializedArguments(newTypeParameters); - // regenerate typeParameters - newClassType.setTypeParameters([]); - genericClassType.typeArguments!.forEach((t) => { - newClassType.addTypeParameter( - genericTypeTransformation( - t, - newTypeParameters, - typeParameters, - context, - ) as TSTypeParameter, + newClassType.setSpecializedArguments(typeArguments); + + if (doSpecialization) { + if (genericClassType.mangledName !== '') { + const genericMangledName = genericClassType.mangledName; + newClassType.mangledName = + genericMangledName.substring( + 0, + genericMangledName.lastIndexOf('|') + 1, + ) + newClassName; + } + + // update specializedTypeCache + TypeResolver.updateSpecializedTypeCache( + genericOwner, + newClassName, + newClassType, ); - }); + } else { + // regenerate typeParameters + newClassType.setTypeParameters([]); + genericClassType.typeArguments!.forEach((t) => { + newClassType.addTypeParameter( + genericCoreProcessor( + t, + typeArguments, + typeParameters, + context, + ) as TSTypeParameter, + ); + }); + } // base class type if (genericClassType.getBase()) { let baseType = genericClassType.getBase(); if (baseType) { if (isTypeGeneric(baseType)) { - baseType = genericClassTransformation( + baseType = genericClassProcessor( baseType, - newTypeParameters, + typeArguments, typeParameters, context, - namePrefix ? namePrefix : undefined, + doSpecialization, + namePrefix, ) as TSClass; } newClassType.setBase(baseType); @@ -1218,11 +735,13 @@ export function genericClassTransformation( const implInfc = genericClassType.getImplInfc(); if (implInfc && isTypeGeneric(implInfc)) { - const newInfcType = genericTypeTransformation( + const newInfcType = genericCoreProcessor( implInfc, - newTypeParameters, + typeArguments, typeParameters, context, + doSpecialization, + namePrefix, ) as TSInterface; newClassType.setImplInfc(newInfcType); } else { @@ -1230,11 +749,12 @@ export function genericClassTransformation( } genericClassType.fields.forEach((field) => { - const newFieldType = genericTypeTransformation( + const newFieldType = genericCoreProcessor( field.type, - newTypeParameters, + typeArguments, typeParameters, context, + doSpecialization, ); newClassType.addMemberField({ name: field.name, @@ -1253,11 +773,13 @@ export function genericClassTransformation( const isOwnMethod = newClassType.overrideOrOwnMethods.has(realFuncName); if (isOwnMethod) { - const newFuncType = genericMethodTransformation( + const newFuncType = genericFunctionProcessor( func.type, - newTypeParameters, + typeArguments, typeParameters, context, + true, + doSpecialization, ) as TSFunction; newFuncType.belongedClass = newClassType; newClassType.addMethod({ @@ -1288,11 +810,12 @@ export function genericClassTransformation( } }); genericClassType.staticFields.forEach((field) => { - const newStaticFieldType = genericTypeTransformation( + const newStaticFieldType = genericCoreProcessor( field.type, - newTypeParameters, + typeArguments, typeParameters, context, + doSpecialization, ); if (newStaticFieldType instanceof TSTypeWithArguments) newClassType.addStaticMemberField({ @@ -1301,48 +824,178 @@ export function genericClassTransformation( }); }); if (genericClassType.ctorType) { - const newCtor = genericMethodTransformation( + const newCtor = genericFunctionProcessor( genericClassType.ctorType, - newTypeParameters, + typeArguments, typeParameters, context, + true, + doSpecialization, ) as TSFunction; newCtor.belongedClass = newClassType; newClassType.ctorType = newCtor; newClassType.ctorType.returnType = newClassType; } - if (genericClassType.belongedScope) { - const typeNames = new Array(); - newTypeParameters.forEach((v) => { - typeNames.push(`${(v as TSTypeParameter).name}`); + if (genericClassType.belongedScope) + if (doSpecialization) { + genericClassType.belongedScope.parent?.addType( + newClassType.className, + newClassType, + ); + } else { + const typeNames = new Array(); + typeArguments.forEach((v) => { + typeNames.push(`${(v as TSTypeParameter).name}`); + }); + const typeSignature = '<' + typeNames.join(',') + '>'; + genericClassType.belongedScope.parent?.addType( + newClassType.className + typeSignature, + newClassType, + ); + } + return newClassType; + } else { + if (doSpecialization) { + /** + * class Base { + * x: number; + * action(param: T) {....} + * } + */ + genericClassType.memberFuncs.forEach((func) => { + if (isTypeGeneric(func.type)) { + const genericFuncType = func.type; + const genericFunctionScope = genericFuncType.belongedScope!; + // generate the name of the specialized function name + const typeNames = new Array(); + typeArguments.forEach((v) => { + if (v.kind !== TypeKind.TYPE_PARAMETER) { + if (v instanceof TSClass) { + typeNames.push(v.className); + } else { + typeNames.push(`${v.kind}`); + } + } + }); + const typeSignature = + typeNames.length > 0 + ? '<' + typeNames.join(',') + '>' + : ''; + const genericFunctionScopeName = + genericFunctionScope.getName(); + const specializedFunctionScopeName = + genericFunctionScopeName + typeSignature; + // whether the specialized generic method already exists + const existMethod = genericClassType.getMethod( + specializedFunctionScopeName, + ); + if (existMethod.method) return genericClassType; + + methodSpecialize(genericFuncType, typeArguments, context); + } }); - const typeSignature = '<' + typeNames.join(',') + '>'; - genericClassType.belongedScope.parent?.addType( - newClassType.className + typeSignature, - newClassType, - ); } - return newClassType; + return genericClassType; } - return genericClassType; } /** - * @describe update the type parameters of the generic type, and return a new generic type - * @param genericType the generic type that need to update type parameter list - * @param newTypeParameters target parameter type list - * @param typeParameters generic parameter type list + * class A { + * a: number; + * echo(param: T) {...}; + * } + * const a = new A(); + * this class type does not contain 'typeParameters'. + */ +export function methodSpecialize( + genericMethodType: TSFunction, + typeArguments: Type[], + context: ParserContext, +) { + const classType = genericMethodType.belongedClass; + const originalFunctionScope = genericMethodType.belongedScope; + const typeParameters = genericMethodType.typeArguments; + + // insufficient information used for specialization + if (!classType || !typeParameters || !originalFunctionScope) return; + + const typeNames = new Array(); + typeArguments.forEach((v) => { + if (v instanceof TSClass) { + typeNames.push(v.className); + } else { + typeNames.push(`${v.kind}`); + } + }); + const typeSignature = '<' + typeNames.join(',') + '>'; + const newFuncName = originalFunctionScope.getName() + typeSignature; + const specializedFunctionType = genericFunctionProcessor( + genericMethodType, + typeArguments, + typeParameters, + context, + true, + true, + ) as TSFunction; + specializedFunctionType.belongedClass = classType; + + // create new function scope begin + const newFuncScope = new FunctionScope(originalFunctionScope.parent!); + originalFunctionScope.specialize(newFuncScope); + newFuncScope.setClassName(classType.className); + newFuncScope.setFuncName(newFuncName); + newFuncScope.setFuncType(specializedFunctionType); + specializedFunctionType.setBelongedScope(newFuncScope); + if (originalFunctionScope.mangledName !== '') { + const genericMangledName = originalFunctionScope.mangledName; + const reverse = genericMangledName.split('|').reverse(); + // class name + reverse[0] = newFuncName; + newFuncScope.mangledName = reverse.reverse().join('|'); + } + newFuncScope.setGenericOwner(originalFunctionScope); + originalFunctionScope.addSpecializedScope(typeSignature, newFuncScope); + const optional = classType.getMethod(originalFunctionScope.getName()) + .method!.optional; + classType.addMethod({ + name: newFuncName, + type: specializedFunctionType, + optional: optional, + }); + classType.overrideOrOwnMethods.add(newFuncName); + + // specialize a generic member function on the inheritance chain + const drivedClasses = classType.getDrivedClasses(); + if (!drivedClasses) return; + drivedClasses.forEach((c) => { + if (c.memberFuncs.length > 0) { + c.memberFuncs.forEach((m) => { + methodSpecialize(m.type, typeArguments, context); + }); + } + }); +} + +/** + * @describe specialize a generic type OR update the generic typeParameters + * @param genericType the generic type that need to be updated or specialized + * @param typeArguments type arguments + * @param typeParameters generic parameter types * @param context the parser context + * @param doSpecialization specialize the generic type when it is true; update the generic typeParameters when it is false + * @param namePrefix in the generic inheritance chain, we will generate a new base class, and the new base class will be renamed * @returns a new generic type */ -export function genericTypeTransformation( +export function genericCoreProcessor( genericType: Type, - newTypeParameters: Type[], + typeArguments: Type[], typeParameters: TSTypeParameter[], context: ParserContext, + doSpecialization = false, + namePrefix?: string, ): Type { - // the type that need to be update must be generic + // the type being processed must be generic type if (!isTypeGeneric(genericType)) return genericType; switch (genericType.kind) { @@ -1363,11 +1016,13 @@ export function genericTypeTransformation( } case TypeKind.ARRAY: { return new TSArray( - genericTypeTransformation( + genericCoreProcessor( (genericType as TSArray).elementType, - newTypeParameters, + typeArguments, typeParameters, context, + doSpecialization, + namePrefix, ), ); } @@ -1378,11 +1033,13 @@ export function genericTypeTransformation( if (t.kind == TypeKind.UNDEFINED) { newUnion.addType(t); } else { - const newType = genericTypeTransformation( + const newType = genericCoreProcessor( t, - newTypeParameters, + typeArguments, typeParameters, context, + doSpecialization, + namePrefix, ); newUnion.addType(newType); } @@ -1390,30 +1047,35 @@ export function genericTypeTransformation( return newUnion; } case TypeKind.FUNCTION: { - const newFuncType = genericFuncTransformation( + const newFuncType = genericFunctionProcessor( genericType as TSFunction, - newTypeParameters, + typeArguments, typeParameters, context, + false, + doSpecialization, ); + return newFuncType; } case TypeKind.CLASS: case TypeKind.INTERFACE: { - const newClassType = genericClassTransformation( + const newClassType = genericClassProcessor( genericType as TSClass, - newTypeParameters, + typeArguments, typeParameters, context, + doSpecialization, + namePrefix, ); return newClassType; } case TypeKind.TYPE_PARAMETER: { const gType = genericType as TSTypeParameter; - if (newTypeParameters && typeParameters) { + if (typeArguments && typeParameters) { for (let i = 0; i < typeParameters.length; i++) { if (typeParameters[i].name === gType.name) { - return newTypeParameters[i]; + return typeArguments[i]; } } } @@ -1433,6 +1095,13 @@ export function genericTypeTransformation( * @param typeArguments specialized type arguments list * @param typeParameters generic parameter type list * @param context the parser context + * @param namePrefix in the generic inheritance chain, we will generate a new base class, and the new base class will be renamed. + * e.g. + * class A {...}; + * class B extends A {...} + * + * In this case, we need to generate a new generic type B_A, so that its specialization operation will not affect its original type A. + * At this time, the namePrefix is 'B_'. * @returns a new type */ export function processGenericType( @@ -1440,6 +1109,7 @@ export function processGenericType( typeArguments: Type[], typeParameters: TSTypeParameter[], context: ParserContext, + namePrefix?: string, ): Type { if ( !isTypeGeneric(genericType) || @@ -1486,53 +1156,37 @@ export function processGenericType( } } if (!isSame) - newType = genericTypeTransformation( + newType = genericCoreProcessor( genericType, typeArguments, typeParameters, context, + false, + namePrefix, ); } else { if (genericType instanceof TSTypeWithArguments) { - const typeArgumentsSignature: Array = []; + let typeSignature = ''; const _typeParameters = genericType.typeArguments; if (_typeParameters) { - _typeParameters.forEach((type) => { - const index = typeParameters.findIndex((t) => { - return t.name === type.name; - }); - if (index == -1) { - throw new UnimplementError( - `${type.name} not found in typeParameters`, - ); - } - typeArgumentsSignature.push(`${typeArguments[index].kind}`); - }); - } - - const genericOwner = genericType.genericOwner - ? genericType.genericOwner - : genericType; - const typeSignature = - typeArgumentsSignature.length > 0 - ? '<' + typeArgumentsSignature.join(',') + '>' - : ''; - const cache = TypeResolver.specializedTypeCache.get(genericOwner); - let found: Type | undefined; - if (cache) { - cache.forEach((v) => { - if (v.has(typeSignature)) { - found = v.get(typeSignature); - } - }); + typeSignature = getTypeSignature( + _typeParameters, + typeParameters, + typeArguments, + ); } + const found = TypeResolver.findTypeInSpecialiezedTypeCache( + genericType, + typeSignature, + ); if (found) return found; } - newType = genericTypeSpecialization( + newType = genericCoreProcessor( genericType, typeArguments, typeParameters, context, + true, ); if (genericType instanceof TSTypeWithArguments) { const genericOwner = genericType.genericOwner @@ -1569,6 +1223,7 @@ export function createScopeBySpecializedType( createFunctionScope( specializeType as TSFunction, parentScope, + false, context, ); break; @@ -1628,9 +1283,10 @@ function createClassScope( const funcKind = genericFuncScope.funcType.funcKind; // constructor is not in the memberFuncs if (funcKind == FunctionKind.CONSTRUCTOR) { - createMethodScope( + createFunctionScope( specializedClassType.ctorType, newClassScope, + true, context, ); } else { @@ -1648,9 +1304,10 @@ function createClassScope( if (res !== -1) { const specializedFunctionType = specializedClassType.memberFuncs[res].type; - createMethodScope( + createFunctionScope( specializedFunctionType, newClassScope, + true, context, ); } @@ -1671,88 +1328,25 @@ function createClassScope( return newClassScope; } -/** - * @describe create a new specialize method FunctionScope - * @param specializedMethodType the new specialized method type - * @param parentScope the parent class scope - * @param context the parser context - * @returns a new specialized method FunctionScope - */ -function createMethodScope( - specializedMethodType: TSFunction, - parentScope: ClassScope, - context: ParserContext, -) { - const classType = parentScope.classType; - const typeArguments = classType.specializedArguments - ? classType.specializedArguments - : specializedMethodType.specializedArguments; - - if ( - !specializedMethodType.genericOwner || - (specializedMethodType.genericOwner && - !specializedMethodType.genericOwner.belongedScope) || - !typeArguments - ) - return; - - const genericMethodType = specializedMethodType.genericOwner as TSFunction; - const genericMethodScope = - genericMethodType.belongedScope! as FunctionScope; - const newMethodScope = new FunctionScope(parentScope); - genericMethodScope.specialize(newMethodScope); - newMethodScope.setClassName(classType.className); - newMethodScope.setFuncName(genericMethodScope.getName()); - - newMethodScope.setGenericOwner(genericMethodScope); - genericMethodScope.addSpecializedScope(classType.className, newMethodScope); - - newMethodScope.setFuncType(specializedMethodType); - specializedMethodType.setBelongedScope(newMethodScope); - - const typeParameters = genericMethodType.belongedClass!.typeArguments!; - if (!typeArguments) return; - - // process funcName, mangledName and className of FunctionScope - if (genericMethodScope.mangledName !== '') { - const genericMangledName = genericMethodScope.mangledName; - const reverse = genericMangledName.split('|').reverse(); - // class name - reverse[1] = classType.className; - newMethodScope.mangledName = reverse.reverse().join('|'); - } - - // process function parameters and create scope - for (let idx = 0; idx < genericMethodType.getParamTypes().length; idx++) { - const genericParamType = genericMethodType.getParamTypes()[idx]; - if ( - genericParamType instanceof TSTypeWithArguments && - genericParamType.belongedScope - ) - createScopeBySpecializedType( - specializedMethodType.getParamTypes()[ - idx - ] as TSTypeWithArguments, - genericParamType.belongedScope.parent!, - context, - ); - } - return newMethodScope; -} - /** * @describe create a new specialize FunctionScope * @param specializedFunctionType the new specialized function type * @param parentScope the parent scope + * @param isMethod method or not * @param context the parser context * @returns a new specialized FunctionScope */ function createFunctionScope( specializedFunctionType: TSFunction, parentScope: Scope, + isMethod: boolean, context: ParserContext, ) { - const typeArguments = specializedFunctionType.specializedArguments; + const typeArguments = isMethod + ? (parentScope as ClassScope).classType.specializedArguments + ? (parentScope as ClassScope).classType.specializedArguments + : specializedFunctionType.specializedArguments + : specializedFunctionType.specializedArguments; if ( !specializedFunctionType.genericOwner || (specializedFunctionType.genericOwner && @@ -1764,42 +1358,74 @@ function createFunctionScope( const genericFuncType = specializedFunctionType.genericOwner as TSFunction; const genericFunctionScope = genericFuncType.belongedScope! as FunctionScope; + + let typeSignature = ''; // check if a specialized scope already exists - const typeArgumentsSignature: Array = []; - typeArguments.forEach((t) => { - if (t.kind !== TypeKind.TYPE_PARAMETER) { - typeArgumentsSignature.push(`${t.kind}`); - } else { - typeArgumentsSignature.push(`${(t as TSTypeParameter).name}`); - } - }); - const typeSignature = - typeArgumentsSignature.length > 0 - ? '<' + typeArgumentsSignature.join(',') + '>' - : ''; - const newFuncName = genericFunctionScope.getName() + typeSignature; - if ( - genericFunctionScope.specializedScopes && - genericFunctionScope.specializedScopes.has(newFuncName) - ) - return; + if (!isMethod) { + const typeArgumentsSignature: Array = []; + typeArguments.forEach((t) => { + if (t.kind !== TypeKind.TYPE_PARAMETER) { + if (t instanceof TSClass) { + typeArgumentsSignature.push(t.className); + } else { + typeArgumentsSignature.push(`${t.kind}`); + } + } else { + typeArgumentsSignature.push(`${(t as TSTypeParameter).name}`); + } + }); + typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + typeArgumentsSignature.join(',') + '>' + : ''; + if ( + genericFunctionScope.specializedScopes && + genericFunctionScope.specializedScopes.has( + genericFunctionScope.getName() + typeSignature, + ) + ) + return; + } const newFuncScope = new FunctionScope(parentScope); - genericFunctionScope.specialize(newFuncScope); - newFuncScope.setClassName(''); - newFuncScope.setFuncName(newFuncName); newFuncScope.setGenericOwner(genericFunctionScope); - genericFunctionScope.addSpecializedScope(newFuncName, newFuncScope); + genericFunctionScope.specialize(newFuncScope); + + if (isMethod) { + newFuncScope.setClassName((parentScope as ClassScope).className); + newFuncScope.setFuncName(genericFunctionScope.getName()); + genericFunctionScope.addSpecializedScope( + (parentScope as ClassScope).className, + newFuncScope, + ); + } else { + newFuncScope.setFuncName( + genericFunctionScope.getName() + typeSignature, + ); + genericFunctionScope.addSpecializedScope( + genericFunctionScope.getName() + typeSignature, + newFuncScope, + ); + } + newFuncScope.setFuncType(specializedFunctionType); specializedFunctionType.setBelongedScope(newFuncScope); - const typeParameters = genericFuncType.typeArguments; + const typeParameters = isMethod + ? genericFuncType.belongedClass!.typeArguments + : genericFuncType.typeArguments; if (!typeParameters) return; + // process funcName, mangledName and className of FunctionScope if (genericFunctionScope.mangledName !== '') { const genericMangledName = genericFunctionScope.mangledName; const reverse = genericMangledName.split('|').reverse(); - reverse[0] = newFuncName; + if (isMethod) { + // class name + reverse[1] = (parentScope as ClassScope).className; + } else { + reverse[0] = genericFunctionScope.getName() + typeSignature; + } newFuncScope.mangledName = reverse.reverse().join('|'); } @@ -1846,9 +1472,24 @@ export function isTypeGeneric(type: Type): boolean { return isTypeGeneric(t); }); } + case TypeKind.TUPLE: { + const tuple = type as TSTuple; + const typeArr = tuple.elements; + return typeArr.some((type) => { + return isTypeGeneric(type); + }); + } case TypeKind.ARRAY: { return isTypeGeneric((type as TSArray).elementType); } + case TypeKind.WASM_ARRAY: { + const arr = (type as WasmArrayType).arrayType; + return isTypeGeneric(arr.elementType); + } + case TypeKind.WASM_STRUCT: { + const tuple = (type as WasmStructType).tupleType; + return isTypeGeneric(tuple); + } case TypeKind.FUNCTION: { const funcType = type as TSFunction; if ( @@ -1902,7 +1543,7 @@ export function isTypeGeneric(type: Type): boolean { * @returns typeArguments */ // Currently only some basic types can be processed -export function getTypeArgumentsFromParameters( +export function calculateTypeArguments( formalParameters: Type[], typeParameters: TSTypeParameter[], actualParameters: Type[], @@ -2001,6 +1642,39 @@ export function getTypeArgumentsFromParameters( return typeArguments; } +/** + * @describe generate signature from 'typeParameters' and 'typeArguments' + * @param parameters the actual type parameters, for example: [Y] + * @param typeParameters the type parameters collection, for example: [X, Y, Z] + * @param typeArguments the type arguments collection, for example: [number, string, boolean] + * @returns type signature, for example: '' + */ +export function getTypeSignature( + parameters: TSTypeParameter[], + typeParameters: TSTypeParameter[], + typeArguments: Type[], +) { + const typeNames: Array = []; + parameters.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new UnimplementError( + `${type.name} not found in typeParameters`, + ); + } + if (typeArguments[index] instanceof TSClass) { + typeNames.push((typeArguments[index] as TSClass).className); + } else { + typeNames.push(`${typeArguments[index].kind}`); + } + }); + const typeSignature = + typeNames.length > 0 ? '<' + typeNames.join(',') + '>' : ''; + return typeSignature; +} + export enum PredefinedTypeId { VOID = 1, UNDEFINED, @@ -2059,6 +1733,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, @@ -2088,6 +1765,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')) { @@ -2166,6 +1865,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; @@ -2173,21 +1951,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/builtin_array.ts b/tests/samples/builtin_array.ts index 589233d1..55ba1f63 100644 --- a/tests/samples/builtin_array.ts +++ b/tests/samples/builtin_array.ts @@ -10,8 +10,14 @@ export function length() { return aLen; } -export function isArray() { +export function isArray_anyType() { const a: any = [1, 2, 3]; const b = Array.isArray(a); return b; } + +export function isArray_arrayType() { + const a = [1, 2, 3]; + const b = Array.isArray(a); + return b; +} 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/unsigned_value.ts b/tests/samples/unsigned_value.ts new file mode 100644 index 00000000..417defc3 --- /dev/null +++ b/tests/samples/unsigned_value.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +export function random_f64() { + let m_w: f64 = 123456789; + let m_z: f64 = 987654321; + let mask: f64 = 0xffffffff; + m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask; + m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask; + let result: f64 = (((m_z << 16) + (m_w & 65535)) >>> 0) / 4294967296; + return result; +} + +export function random_i64() { + let m_w: i64 = 123456789; + let m_z: i64 = 987654321; + let mask: i64 = 0xffffffff; + m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask; + m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask; + let result: i64 = (((m_z << 16) + (m_w & 65535)) >>> 0) / 4294967296; + return result; +} + +export function random_i32() { + let m_w: i32 = 123456789; + let m_z: i32 = 987654321; + let mask: i32 = 0xffffffff; + m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask; + m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask; + let result: i32 = (((m_z << 16) + (m_w & 65535)) >>> 0) / 4294967296; + return result; +} + +export function random_f32() { + let m_w: f32 = 123456789; + let m_z: f32 = 987654321; + let mask: f32 = 0xffffffff; + m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask; + m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask; + let result: f32 = (((m_z << 16) + (m_w & 65535)) >>> 0) / 4294967296; + return result; +} 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 fecdb4cc..a90c2ad1 100644 --- a/tools/validate/wamr/validation.json +++ b/tools/validate/wamr/validation.json @@ -1349,7 +1349,12 @@ "result": "3:f64" }, { - "name": "isArray", + "name": "isArray_anyType", + "args": [], + "result": "0x1:i32" + }, + { + "name": "isArray_arrayType", "args": [], "result": "0x1:i32" } @@ -4213,6 +4218,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": [ @@ -4528,6 +4578,31 @@ } ] }, + { + "module": "unsigned_value", + "entries": [ + { + "name": "random_f64", + "args": [], + "result": "0.7322977:f64" + }, + { + "name": "random_i64", + "args": [], + "result": "0:f64" + }, + { + "name": "random_i32", + "args": [], + "result": "0:f64" + }, + { + "name": "random_f32", + "args": [], + "result": "0:f64" + } + ] + }, { "module": "decimalization", "entries": [ @@ -4609,27 +4684,77 @@ { "name": "xor", "args": [], - "result": "4656\n4294967294\n4656" + "result": "4656\n-2\n4656" }, { "name": "and", "args": [], - "result": "4\n4294967295\n4" + "result": "4\n-1\n4" }, { "name": "or", "args": [], - "result": "4660\n4294967295\n4660" + "result": "4660\n-1\n4660" }, { "name": "shl", "args": [], - "result": "-2\n4294967294\n8" + "result": "-2\n-2\n8" }, { "name": "shr", "args": [], - "result": "1073741823\n1073741824\n34359738367\n34359738367\n2\n2147483648" + "result": "1073741823\n1073741824\n-1\n2147483647\n2\n2147483648" + } + ] + }, + { + "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" } ] },