From 4a27753b1e24f63fc3c0b3410e20806b5ef51dd2 Mon Sep 17 00:00:00 2001 From: chibash Date: Thu, 21 Nov 2024 10:35:40 +0900 Subject: [PATCH] changes the implementation of value_t arrays --- microcontroller/core/include/c-runtime.h | 9 +- microcontroller/core/src/c-runtime.c | 60 +++++++------ microcontroller/core/test/c-runtime-test.c | 2 +- microcontroller/core/test/c-runtime-test2.c | 4 +- microcontroller/core/test/profiler-test.c | 2 +- server/src/jit/jit-code-generator.ts | 2 +- .../transpiler/code-generator/c-runtime.ts | 33 +++++--- .../code-generator/code-generator.ts | 37 ++++---- .../transpiler/code-generator/variables.ts | 54 +++++++++++- server/src/transpiler/type-checker.ts | 2 + server/tests/jit/jit-code-generator.test.ts | 2 +- .../code-generator/code-generator.test.ts | 10 ++- .../code-generator/code-generator2.test.ts | 84 +++++++++++++++++++ 13 files changed, 227 insertions(+), 74 deletions(-) diff --git a/microcontroller/core/include/c-runtime.h b/microcontroller/core/include/c-runtime.h index 3bc7782..17a801a 100644 --- a/microcontroller/core/include/c-runtime.h +++ b/microcontroller/core/include/c-runtime.h @@ -60,13 +60,14 @@ typedef struct class_object { // If start_index is 2, body[0] and body[1] don't hold a pointer. const char* const name; // printable class name const struct class_object* const superclass; // super class or NULL + uint32_t flags; // 1: array type. An array type supports [] and .length. struct property_table table; void* vtbl[1]; } class_object; // A macro for declaring a class_object. // n: the length of body (> 0). -#define CLASS_OBJECT(name, n) ALGIN const union { struct class_object clazz; struct { uint32_t s; uint32_t i; const char* const cn; const struct class_object* const sc; struct property_table pt; void* vtbl[n]; } body; } name +#define CLASS_OBJECT(name, n) ALGIN const union { struct class_object clazz; struct { uint32_t s; uint32_t i; const char* const cn; const struct class_object* const sc; uint32_t f; struct property_table pt; void* vtbl[n]; } body; } name inline int32_t value_to_int(value_t v) { return (int32_t)v / 4; } inline value_t int_to_value(int32_t v) { return (uint32_t)v << 2; } @@ -248,10 +249,10 @@ extern value_t CR_SECTION gc_vector_get(value_t obj, int32_t index); extern value_t CR_SECTION gc_vector_set(value_t obj, int32_t index, value_t new_value); extern value_t CR_SECTION gc_make_vector(int32_t n, ...); -extern value_t CR_SECTION safe_value_to_array(value_t v); +extern bool CR_SECTION gc_is_instance_of_array(value_t obj); extern value_t CR_SECTION safe_value_to_anyarray(value_t v); -extern value_t CR_SECTION gc_new_array(int32_t is_any, int32_t n, value_t init_value); -extern value_t CR_SECTION gc_make_array(int32_t is_any, int32_t n, ...); +extern value_t CR_SECTION gc_new_array(const class_object* clazz, int32_t n, value_t init_value); +extern value_t CR_SECTION gc_make_array(const class_object* clazz, int32_t n, ...); extern int32_t CR_SECTION gc_array_length(value_t obj); extern value_t* CR_SECTION gc_array_get(value_t obj, int32_t index); extern value_t CR_SECTION gc_array_set(value_t obj, int32_t index, value_t new_value); diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index abeb816..e83b495 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -479,10 +479,11 @@ void* method_lookup(value_t obj, uint32_t index) { return get_objects_class(value_to_ptr(obj))->vtbl[index]; } +#define IS_ARRAY_TYPE(clazz) (clazz != NULL && (clazz)->flags & 1) #define DEFAULT_PTABLE { .size = 0, .offset = 0, .unboxed = 0, .prop_names = NULL, .unboxed_types = NULL } CLASS_OBJECT(object_class, 1) = { - .clazz = { .size = 0, .start_index = 0, .name = "Object", .superclass = NULL, .table = DEFAULT_PTABLE }}; + .clazz = { .size = 0, .start_index = 0, .name = "Object", .superclass = NULL, .flags = 0, .table = DEFAULT_PTABLE }}; static pointer_t allocate_heap(uint16_t word_size); @@ -630,7 +631,7 @@ value_t acc_anyobj_property(value_t obj, char op, int property, value_t value) { static CLASS_OBJECT(function_object, 0) = { .clazz = { .size = 3, .start_index = 2, .name = "Function", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; + .superclass = &object_class.clazz, .flags = 0, .table = DEFAULT_PTABLE }}; // this_object may be VALUE_UNDEF. value_t gc_new_function(void* fptr, const char* signature, value_t captured_values) { @@ -668,10 +669,10 @@ value_t gc_function_captured_value(value_t obj, int index) { // or one primitive value. They are used for implementing a free variable. static CLASS_OBJECT(boxed_value, 0) = { .clazz.size = 1, .clazz.start_index = 0, - .clazz.name = "boxed_value", .clazz.superclass = NULL, .clazz.table = DEFAULT_PTABLE }; + .clazz.name = "boxed_value", .clazz.superclass = NULL, .clazz.flags = 0, .clazz.table = DEFAULT_PTABLE }; static CLASS_OBJECT(boxed_raw_value, 0) = { .clazz.size = 1, .clazz.start_index = SIZE_NO_POINTER, - .clazz.name = "boxed_raw_value", .clazz.superclass = NULL, .clazz.table = DEFAULT_PTABLE }; + .clazz.name = "boxed_raw_value", .clazz.superclass = NULL, .clazz.flags = 0, .clazz.table = DEFAULT_PTABLE }; value_t gc_new_box(value_t value) { ROOT_SET(rootset, 1) @@ -698,7 +699,7 @@ value_t gc_new_float_box(float value) { // This C string is not allocated in the heap memory managed by the garbage collector. static CLASS_OBJECT(string_literal, 0) = { .clazz.size = 1, .clazz.start_index = SIZE_NO_POINTER, - .clazz.name = "string", .clazz.superclass = NULL, .clazz.table = DEFAULT_PTABLE }; + .clazz.name = "string", .clazz.superclass = NULL, .clazz.flags = 0, .clazz.table = DEFAULT_PTABLE }; // str: a char array in the C language. value_t gc_new_string(char* str) { @@ -730,7 +731,7 @@ bool gc_is_string_object(value_t obj) { static CLASS_OBJECT(intarray_object, 1) = { .clazz = { .size = -1, .start_index = SIZE_NO_POINTER, .name = "Array", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; + .superclass = &object_class.clazz, .flags = 1, .table = DEFAULT_PTABLE }}; value_t safe_value_to_intarray(value_t v) { return safe_value_to_value(&intarray_object.clazz, v); @@ -798,7 +799,7 @@ bool gc_is_intarray(value_t v) { static CLASS_OBJECT(floatarray_object, 1) = { .clazz = { .size = -1, .start_index = SIZE_NO_POINTER, .name = "Array", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; + .superclass = &object_class.clazz, .flags = 1, .table = DEFAULT_PTABLE }}; value_t safe_value_to_floatarray(value_t v) { return safe_value_to_value(&floatarray_object.clazz, v); @@ -865,11 +866,11 @@ bool gc_is_floatarray(value_t v) { CLASS_OBJECT(class_Uint8Array, 1) = { .clazz = { .size = -1, .start_index = SIZE_NO_POINTER, .name = "Uint8Array", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; + .superclass = &object_class.clazz, .flags = 1, .table = DEFAULT_PTABLE }}; static CLASS_OBJECT(boolarray_object, 1) = { .clazz = { .size = -1, .start_index = SIZE_NO_POINTER, .name = "Array", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; + .superclass = &object_class.clazz, .flags = 1, .table = DEFAULT_PTABLE }}; value_t safe_value_to_boolarray(value_t v) { return safe_value_to_value(&boolarray_object.clazz, v); @@ -953,7 +954,7 @@ bool gc_is_boolarray(value_t v) { static CLASS_OBJECT(vector_object, 1) = { .clazz = { .size = -1, .start_index = 1, .name = "Vector", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; + .superclass = &object_class.clazz, .flags = 1, .table = DEFAULT_PTABLE }}; value_t safe_value_to_vector(value_t v) { return safe_value_to_value(&vector_object.clazz, v); @@ -1036,29 +1037,26 @@ value_t gc_make_vector(int32_t n, ...) { return array; } -// An any-type array +// any-type and other arrays -/* this may be Array etc. */ -static CLASS_OBJECT(array_object, 1) = { - .clazz = { .size = 2, .start_index = 1, .name = "Array", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; +// Returns true when obj is an array of any kind of type. +bool gc_is_instance_of_array(value_t obj) { + class_object* clazz = gc_get_class_of(obj); + return IS_ARRAY_TYPE(clazz); +} static CLASS_OBJECT(anyarray_object, 1) = { .clazz = { .size = 2, .start_index = 1, .name = "Array", - .superclass = &object_class.clazz, .table = DEFAULT_PTABLE }}; - -value_t safe_value_to_array(value_t v) { - return safe_value_to_value(&array_object.clazz, v); -} + .superclass = &object_class.clazz, .flags = 1, .table = DEFAULT_PTABLE }}; value_t safe_value_to_anyarray(value_t v) { return safe_value_to_value(&anyarray_object.clazz, v); } -value_t gc_new_array(int32_t is_any, int32_t n, value_t init_value) { +value_t gc_new_array(const class_object* clazz, int32_t n, value_t init_value) { ROOT_SET(rootset, 2) rootset.values[0] = init_value; - pointer_t obj = gc_allocate_object(is_any ? &anyarray_object.clazz : &array_object.clazz); + pointer_t obj = gc_allocate_object(clazz == NULL ? &anyarray_object.clazz : clazz); rootset.values[1] = ptr_to_value(obj); value_t vec = gc_new_vector(n, init_value); obj->body[1] = vec; @@ -1072,9 +1070,9 @@ value_t gc_new_array(int32_t is_any, int32_t n, value_t init_value) { A caller function must guarantee that they are reachable from the root. */ -value_t gc_make_array(int32_t is_any, int32_t n, ...) { +value_t gc_make_array(const class_object* clazz, int32_t n, ...) { va_list args; - value_t array = gc_new_array(is_any, n, VALUE_UNDEF); + value_t array = gc_new_array(clazz, n, VALUE_UNDEF); pointer_t arrayp = value_to_ptr(array); va_start(args, n); @@ -1116,11 +1114,11 @@ value_t gc_array_set(value_t obj, int32_t index, value_t new_value) { int32_t get_all_array_length(value_t obj) { class_object* clazz = gc_get_class_of(obj); - if (clazz == &intarray_object.clazz || clazz == &floatarray_object.clazz - || clazz == &vector_object.clazz || clazz == &array_object.clazz || clazz == &anyarray_object.clazz) - return value_to_ptr(obj)->body[0]; - else if (clazz == &class_Uint8Array.clazz || clazz == &boolarray_object.clazz) - return value_to_ptr(obj)->body[1]; + if (IS_ARRAY_TYPE(clazz)) + if (clazz == &class_Uint8Array.clazz || clazz == &boolarray_object.clazz) + return value_to_ptr(obj)->body[1]; + else + return value_to_ptr(obj)->body[0]; else return -1; } @@ -1146,7 +1144,7 @@ value_t gc_safe_array_get(value_t obj, int32_t idx) { return bool_to_value(*gc_bytearray_get(obj, idx)); else if (clazz == &vector_object.clazz) return gc_vector_get(obj, idx); - else if (clazz == &array_object.clazz || clazz == &anyarray_object.clazz) + else if (IS_ARRAY_TYPE(clazz)) // for arrays of value_t return *gc_array_get(obj, idx); else { runtime_type_error("reading a non array"); @@ -1168,7 +1166,7 @@ value_t gc_safe_array_set(value_t obj, int32_t idx, value_t new_value) { } else if (clazz == &vector_object.clazz) return gc_vector_set(obj, idx, new_value); - else if (clazz == &array_object.clazz || clazz == &anyarray_object.clazz) + else if (IS_ARRAY_TYPE(clazz)) // for arrays of value_t return gc_array_set(obj, idx, new_value); else { runtime_type_error("assignment to a non array"); diff --git a/microcontroller/core/test/c-runtime-test.c b/microcontroller/core/test/c-runtime-test.c index 658ea5c..f7c943d 100644 --- a/microcontroller/core/test/c-runtime-test.c +++ b/microcontroller/core/test/c-runtime-test.c @@ -95,7 +95,7 @@ bool is_live_object(value_t obj) { } static value_t gc_new_array2(int32_t n) { - return gc_new_array(1, n, VALUE_UNDEF); + return gc_new_array(NULL, n, VALUE_UNDEF); } static value_t gc_new_vector2(int32_t n) { diff --git a/microcontroller/core/test/c-runtime-test2.c b/microcontroller/core/test/c-runtime-test2.c index 7146a4d..8037675 100644 --- a/microcontroller/core/test/c-runtime-test2.c +++ b/microcontroller/core/test/c-runtime-test2.c @@ -167,7 +167,7 @@ void test_array2() { } void test_array() { - value_t arr = gc_make_array(1, 3, int_to_value(1), int_to_value(2), int_to_value(3)); + value_t arr = gc_make_array(NULL, 3, int_to_value(1), int_to_value(2), int_to_value(3)); Assert_equals(gc_array_length(arr), 3); *gc_array_get(arr, 2) = int_to_value(4); Assert_equals(*gc_array_get(arr, 2), int_to_value(4)); @@ -180,7 +180,7 @@ void test_string_literal() { value_t str = gc_new_string("foo"); root_set.values[0] = str; value_t i = int_to_value(3); - value_t a = gc_make_array(1, 2, VALUE_FALSE, VALUE_NULL); + value_t a = gc_make_array(NULL, 2, VALUE_FALSE, VALUE_NULL); root_set.values[1] = a; Assert_true(gc_is_string_literal(str)); Assert_true(!gc_is_string_literal(i)); diff --git a/microcontroller/core/test/profiler-test.c b/microcontroller/core/test/profiler-test.c index 30824dd..4210583 100644 --- a/microcontroller/core/test/profiler-test.c +++ b/microcontroller/core/test/profiler-test.c @@ -55,7 +55,7 @@ static void test_converter() { const char* result_str_s = typeint_to_str(typeint_s); Assert_str_equals(result_str_s, "string"); - value_t arr = gc_new_array(true, 2, int_to_value(4)); + value_t arr = gc_new_array(NULL, 2, int_to_value(4)); typeint_t typeint_arr = value_to_typeint(arr); const char* result_str_arr = typeint_to_str(typeint_arr); Assert_str_equals(result_str_arr, "Array"); diff --git a/server/src/jit/jit-code-generator.ts b/server/src/jit/jit-code-generator.ts index 19f749d..8dc149a 100644 --- a/server/src/jit/jit-code-generator.ts +++ b/server/src/jit/jit-code-generator.ts @@ -298,7 +298,7 @@ export class JitCodeGenerator extends CodeGenerator{ const info = fenv.table.lookup(paramName) if (info !== undefined) { const name = info.transpiledName(paramName) - paramSig.push(`${cr.typeConversion(argTypes[i], funcType.paramTypes[i], node)}${name})`); + paramSig.push(`${cr.typeConversion(argTypes[i], funcType.paramTypes[i], fenv, node)}${name})`); } } this.result.write(paramSig.join(', ')).write(');'); diff --git a/server/src/transpiler/code-generator/c-runtime.ts b/server/src/transpiler/code-generator/c-runtime.ts index 0bdbcb7..36b6e7e 100644 --- a/server/src/transpiler/code-generator/c-runtime.ts +++ b/server/src/transpiler/code-generator/c-runtime.ts @@ -7,6 +7,7 @@ import { Integer, Float, BooleanT, StringT, Void, Null, Any, StaticType, isPrimitiveType, typeToString, ArrayType, sameType, encodeType, isSubtype, ByteArrayClass, } from '../types' import { InstanceType, ClassTable } from '../classes' +import { VariableEnv } from './variables' export const anyTypeInC = 'value_t' export const funcTypeInC = 'value_t' @@ -73,7 +74,7 @@ function typeConversionError(from: StaticType | undefined, to: StaticType | unde // returns '(' or '(' // "from" or "to" is undefined when type conversion is unnecessary export function typeConversion(from: StaticType | undefined, to: StaticType | undefined, - node: AST.Node) { + env: VariableEnv, node: AST.Node) { if (from === undefined || to === undefined) return '(' @@ -153,9 +154,7 @@ export function typeConversion(from: StaticType | undefined, to: StaticType | un else if (to.elementType === Any) return 'safe_value_to_anyarray(' else - break - // cannot determine wether a given array - // is an array of String or Any + return `safe_value_to_value(&${env.useArrayType(to)[0]}.clazz, ` } else if (to instanceof InstanceType) if (isSubtype(from, to)) @@ -359,7 +358,8 @@ export function arrayElementSetter(arrayType: StaticType | undefined) { export const accumulateInUnknownArray = 'gc_safe_array_acc' // makes an array object from elements -export function arrayFromElements(t: StaticType) { +export function arrayFromElements(arrayType: ArrayType, env: VariableEnv) { + const t = arrayType.elementType if (t === Integer) return 'gc_make_intarray(' else if (t === Float) @@ -367,12 +367,13 @@ export function arrayFromElements(t: StaticType) { else if (t === BooleanT) return 'gc_make_bytearray(true, ' else if (t === Any) - return 'gc_make_array(1, ' + return 'gc_make_array((void*)0, ' else - return 'gc_make_array(0, ' + return `gc_make_array(&${env.useArrayType(arrayType)[0]}.clazz, ` } -export function arrayFromSize(t: StaticType) { +export function arrayFromSize(arrayType: ArrayType, env: VariableEnv) { + const t = arrayType.elementType if (t === Integer) return 'gc_new_intarray(' else if (t === Float) @@ -380,9 +381,9 @@ export function arrayFromSize(t: StaticType) { else if (t === BooleanT) return 'gc_new_bytearray(true, ' else if (t === Any) - return 'gc_new_array(1, ' + return 'gc_new_array((void*)0, ' else - return 'gc_new_array(0, ' + return `gc_new_array(&${env.useArrayType(arrayType)[0]}.clazz, ` } export function actualElementType(t: StaticType) { @@ -403,7 +404,7 @@ export function getArrayLengthIndex(t: StaticType) { return 0 } -export const runtimeTypeArray = 'array_object' +export const isInstanceOfArray = `gc_is_instance_of_array(` export const stringMaker = 'gc_new_string' export const isStringType = 'gc_is_string_object(' @@ -493,7 +494,7 @@ export function classDeclaration(clazz: InstanceType, classTable: ClassTable) { const propList = `static const uint16_t ${propListName}[] = { ${ptable.props.join(', ')} };` return `${propList}\nCLASS_OBJECT(${classNameInC(name)}, ${table.length}) = { - .body = { .s = ${size}, .i = ${start}, .cn = "${name}", .sc = ${superAddr} , .pt = ${propTable}, .vtbl = { ${tableArray} }}};` + .body = { .s = ${size}, .i = ${start}, .cn = "${name}", .sc = ${superAddr} , .f = 0, .pt = ${propTable}, .vtbl = { ${tableArray} }}};` } export function makeInstance(clazz: InstanceType) { @@ -511,3 +512,11 @@ export function methodLookup(method: [StaticType, number, InstanceType], func: s export function isInstanceOf(t: InstanceType) { return `gc_is_instance_of(&${classObjectNameInC(t.name())}, ` } + +export function arrayTypeDeclaration(type: ArrayType, name: string, is_declared: boolean) { + const typeName = typeToString(type) + if (is_declared) + return `CLASS_OBJECT(${name}, 0) = { .body = { .s = 2, .i = 1, .cn = "${typeName}", .sc = &object_class.clazz, .f = 1, .pt = { .size = 0, .offset = 0, .unboxed = 0, .prop_names = (void*)0, .unboxed_types = (void*)0 }, .vtbl = {}}};\n` + else + return `extern CLASS_OBJECT(${name}, 0);\n` +} diff --git a/server/src/transpiler/code-generator/code-generator.ts b/server/src/transpiler/code-generator/code-generator.ts index 02a7e54..fbd1307 100644 --- a/server/src/transpiler/code-generator/code-generator.ts +++ b/server/src/transpiler/code-generator/code-generator.ts @@ -75,7 +75,8 @@ export class CodeGenerator extends visitor.NodeVisitor { } program(node: AST.Program, env: VariableEnv): void { - const env2 = new GlobalEnv(getVariableNameTable(node), this.globalRootSetName) + const table = getVariableNameTable(node) as GlobalVariableNameTable + const env2 = new GlobalEnv(table, this.globalRootSetName) const size = env2.allocateRootSet() const oldResult = this.result this.result = new CodeWriter().right() @@ -117,6 +118,10 @@ export class CodeGenerator extends visitor.NodeVisitor { this.signatures += `${cr.funcTypeToCType(type, name)};\n` ) + table.getArrayTypes().forEach(([name, type, is_declared], key) => + this.signatures += cr.arrayTypeDeclaration(type, name, is_declared) + ) + this.signatures += `void ${this.initializerName}();\n${cr.declareRootSet(this.globalRootSetName, size)}\n` const numOfVars = env2.getNumOfVars() oldResult.write(`\nvoid ${this.initializerName}() {`) @@ -360,7 +365,7 @@ export class CodeGenerator extends visitor.NodeVisitor { const typeAndVar = cr.typeToCType(retType, cr.returnValueVariable) const type = this.needsCoercion(argument) if (type) - this.result.write(`{ ${typeAndVar} = ${cr.typeConversion(type, retType, node)}`) + this.result.write(`{ ${typeAndVar} = ${cr.typeConversion(type, retType, env, node)}`) else this.result.write(`{ ${typeAndVar} = (`) @@ -567,7 +572,7 @@ export class CodeGenerator extends visitor.NodeVisitor { if (decl.init) { const initType = this.needsCoercion(decl.init) if (initType) { - const converter = cr.typeConversion(initType, info.type, decl) + const converter = cr.typeConversion(initType, info.type, env, decl) this.result.write(` ${withEq ? '=' : ''} ${converter}`) this.visit(decl.init, env) this.result.write(')') @@ -801,7 +806,7 @@ export class CodeGenerator extends visitor.NodeVisitor { return } else if (node.operator === '~') - this.result.write(`~${cr.typeConversion(type, Integer, node)}`) + this.result.write(`~${cr.typeConversion(type, Integer, env, node)}`) else this.result.write('(') else { @@ -906,9 +911,9 @@ export class CodeGenerator extends visitor.NodeVisitor { // if either left or right operand is boolean, the other is boolean || (left_type === StringT || right_type === StringT) || (left_type === Any || right_type === Any)) { - this.result.write(`${cr.arithmeticOpForAny(op2)}(${cr.typeConversion(left_type, Any, left)}`) + this.result.write(`${cr.arithmeticOpForAny(op2)}(${cr.typeConversion(left_type, Any, env, left)}`) this.visit(left, env) - this.result.write(`), ${cr.typeConversion(right_type, Any, right)}`) + this.result.write(`), ${cr.typeConversion(right_type, Any, env, right)}`) this.visit(right, env) this.result.write('))') } @@ -921,9 +926,9 @@ export class CodeGenerator extends visitor.NodeVisitor { const left_type = this.needsCoercion(left) const right_type = this.needsCoercion(right) if (left_type === Any || right_type === Any || left_type === StringT) { - this.result.write(`${cr.arithmeticOpForAny(op)}(${cr.typeConversion(left_type, Any, left)}`) + this.result.write(`${cr.arithmeticOpForAny(op)}(${cr.typeConversion(left_type, Any, env, left)}`) this.visit(left, env) - this.result.write(`), ${cr.typeConversion(right_type, Any, right)}`) + this.result.write(`), ${cr.typeConversion(right_type, Any, env, right)}`) this.visit(right, env) this.result.write('))') } @@ -957,6 +962,8 @@ export class CodeGenerator extends visitor.NodeVisitor { const type = getStaticType(right) if (type === StringT) this.result.write(cr.isStringType) + else if (type instanceof ArrayType) + this.result.write(cr.isInstanceOfArray) else if (type instanceof InstanceType) this.result.write(cr.isInstanceOf(type)) else @@ -1080,7 +1087,7 @@ export class CodeGenerator extends visitor.NodeVisitor { env: VariableEnv) { let func: string if (left_type === Any || right_type === Any) - func = cr.typeConversion(right_type, left_type, right) + func = cr.typeConversion(right_type, left_type, env, right) else func = '(' @@ -1281,7 +1288,7 @@ export class CodeGenerator extends visitor.NodeVisitor { if (arg_type === undefined) this.visit(arg, env) else { - this.result.write(cr.typeConversion(arg_type, type, arg)) + this.result.write(cr.typeConversion(arg_type, type, env, arg)) this.visit(arg, env) this.result.write(')') } @@ -1310,7 +1317,7 @@ export class CodeGenerator extends visitor.NodeVisitor { } newArrayExpression(node: AST.NewExpression, atype: ArrayType, env: VariableEnv): void { - this.result.write(cr.arrayFromSize(atype.elementType)) + this.result.write(cr.arrayFromSize(atype, env)) let numOfObjectArgs = this.callExpressionArg(node.arguments[0], Integer, env) this.result.write(', ') @@ -1356,14 +1363,14 @@ export class CodeGenerator extends visitor.NodeVisitor { if (!(atype instanceof ArrayType)) throw this.errorLog.push(`bad array expression`, node) - this.result.write(cr.arrayFromElements(atype.elementType) + node.elements.length) + this.result.write(cr.arrayFromElements(atype, env) + node.elements.length) let numOfObjectArgs = 0 for (const ele of node.elements) if (ele !== null) { this.result.write(', ') numOfObjectArgs += this.addToRootSet(ele, atype.elementType, env) const type = getStaticType(ele) - this.result.write(cr.typeConversion(type, atype.elementType, ele)) + this.result.write(cr.typeConversion(type, atype.elementType, env, ele)) this.visit(ele, env) this.result.write(')') } @@ -1403,7 +1410,7 @@ export class CodeGenerator extends visitor.NodeVisitor { this.result.write(`, ${typeAndIndex[1]})`) } else { - this.result.write(`${cr.typeConversion(Any, typeAndIndex[0], node)}${cr.getObjectProperty}(`) + this.result.write(`${cr.typeConversion(Any, typeAndIndex[0], env, node)}${cr.getObjectProperty}(`) this.visit(node.object, env) this.result.write(`, ${typeAndIndex[1]}))`) } @@ -1475,7 +1482,7 @@ export class CodeGenerator extends visitor.NodeVisitor { tsAsExpression(node: AST.TSAsExpression, env: VariableEnv): void { const exprType = getStaticType(node.expression) const asType = getStaticType(node) - this.result.write(`${cr.typeConversion(exprType, asType, node)}`) + this.result.write(`${cr.typeConversion(exprType, asType, env, node)}`) this.visit(node.expression, env) this.result.write(')') } diff --git a/server/src/transpiler/code-generator/variables.ts b/server/src/transpiler/code-generator/variables.ts index a654c28..cbe4dd1 100644 --- a/server/src/transpiler/code-generator/variables.ts +++ b/server/src/transpiler/code-generator/variables.ts @@ -1,7 +1,7 @@ // Copyright (C) 2023- Shigeru Chiba. All rights reserved. import * as AST from '@babel/types' -import { Null, FunctionType, StaticType, ObjectType, isPrimitiveType } from '../types' +import { Null, FunctionType, StaticType, ObjectType, isPrimitiveType, ArrayType } from '../types' import { NameTable, NameTableMaker, GlobalNameTable, BlockNameTable, FunctionNameTable, NameInfo, getNameTable } from '../names' @@ -187,11 +187,18 @@ class FunctionVarNameTable extends FunctionNameTable { isFreeInfo(free: NameInfo): boolean { return free instanceof FreeVariableInfo } } +let arrayTypeIndex = 0 + export class GlobalVariableNameTable extends GlobalNameTable { private classTableObject?: ClassTable + private arrayTypes: Map + private parentGlobalTable?: GlobalVariableNameTable - constructor(parent?: NameTable) { + constructor(parent?: GlobalVariableNameTable) { super(parent) + this.arrayTypes = new Map() + this.parentGlobalTable = parent + if (parent) this.classTableObject = undefined else @@ -210,6 +217,37 @@ export class GlobalVariableNameTable extends GlobalNameTable { else throw new Error('fatal: not class table found') } + + getArrayTypes() { return this.arrayTypes } + + // useArrayType() returns [clazz_name_in_C, whether_it_must_be_declared_in_C]. + // The second element is false when the class type for the array is imported by 'extern'. + useArrayType(type: ArrayType) { + const name = type.name() + const info = this.arrayTypes.get(name) + if (info !== undefined) + return info + else { + const found = this.findUsedArrayType(name) + const info: [string, ArrayType, boolean] + = [ found ? found[0] : `array_type${arrayTypeIndex++}`, type, !found ] + this.arrayTypes.set(name, info) + return info + } + } + + private findUsedArrayType(name: string) { + let tbl = this.parentGlobalTable + while (tbl !== undefined) { + const info = tbl.arrayTypes?.get(name) + if (info !== undefined) + return info + else + tbl = tbl.parentGlobalTable + } + + return undefined + } } export function getVariableNameTable(node: AST.Node): NameTable { @@ -277,6 +315,18 @@ export class VariableEnv { f(info, key) }) } + + + useArrayType(type: ArrayType): [ string, ArrayType, boolean ] { + let env: VariableEnv | null = this + while (env !== null) + if (env.table instanceof GlobalVariableNameTable) + return env.table.useArrayType(type) + else + env = env.parent + + throw new Error('internal error: cannot find a GlobalVariableNameTable for useArrayType()') + } } export class FunctionEnv extends VariableEnv { diff --git a/server/src/transpiler/type-checker.ts b/server/src/transpiler/type-checker.ts index 785d6dc..618ac6e 100644 --- a/server/src/transpiler/type-checker.ts +++ b/server/src/transpiler/type-checker.ts @@ -743,6 +743,8 @@ export default class TypeChecker extends visitor.NodeVisi type = info.type else if (typeName === 'string') type = StringT + else if (typeName === 'Array') + type = new ArrayType(Any) // set type to Array when the expression is "_ instanceof Array". else this.assert(false, `invalid type name: ${typeName}`, node.right) diff --git a/server/tests/jit/jit-code-generator.test.ts b/server/tests/jit/jit-code-generator.test.ts index cf26da4..0f18bc2 100644 --- a/server/tests/jit/jit-code-generator.test.ts +++ b/server/tests/jit/jit-code-generator.test.ts @@ -106,7 +106,7 @@ print(aarr0(aarr, arr)); const file1 = tempCFilePath('file1') const result1 = compile(0, src, profiler, file1, result0.names) expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) - .toEqual("Array, Array, undefined, undefined\n1\n") + .toEqual("Array, string[], undefined, undefined\n1\n") }) test('profile: class', () => { diff --git a/server/tests/transpiler/code-generator/code-generator.test.ts b/server/tests/transpiler/code-generator/code-generator.test.ts index b2ad922..a0d859c 100644 --- a/server/tests/transpiler/code-generator/code-generator.test.ts +++ b/server/tests/transpiler/code-generator/code-generator.test.ts @@ -1487,17 +1487,19 @@ test('convert an any-type value to an array', () => { const arr3a: any = new Array(n) const arr4a: any = new Array(n, 'foo') const arr5a: any = new Array(n) + const arr6a: any = new Array(n) const arr1: integer[] = arr1a const arr2: float[] = arr2a const arr3: boolean[] = arr3a - typeof arr4a - const arr5: any[] = arr5a + print(typeof arr4a) const arr4: string[] = arr4a + const arr5: any[] = arr5a + const arr6: any[] = arr6a print(arr1[0]) } foo(3) ` - expect(() => compileAndRun(src)).toThrow(/cannot convert any to string\[\]/) + expect(compileAndRun(src)).toBe('any\n0\n') }) test('array length', () => { @@ -2343,7 +2345,7 @@ test('accumulation in properties of any-type objects', () => { expect(() => { compileAndRun(src + src5)}).toThrow(/no such property/) }) -test('an array object bound to a any-type variable', () => { +test('an array object bound to an any-type variable', () => { const maker = (init: string, v1: string, v2: string, v3: string) => ` const a1 = ${init} print(typeof a1) diff --git a/server/tests/transpiler/code-generator/code-generator2.test.ts b/server/tests/transpiler/code-generator/code-generator2.test.ts index 0f2c1eb..76ff579 100644 --- a/server/tests/transpiler/code-generator/code-generator2.test.ts +++ b/server/tests/transpiler/code-generator/code-generator2.test.ts @@ -484,3 +484,87 @@ test('Uint8Array is a leaf class', () => { expect(() => compileAndRun(src, destFile)).toThrow(/invalid super class/) }) + +test.only('instanceof array', () => { + const src = ` + class Foo {} + const a1 = [1, 2, 3] + const a2 = [0.1, 0.2] + const a3 = ['foo', 'bar'] + const a4 = [new Foo()] + print(a1 instanceof Array) + print(a2 instanceof Array) + print(a3 instanceof Array) + print(a4 instanceof Array) + ` + + expect(compileAndRun(src, destFile)).toBe('true\ntrue\ntrue\ntrue\n') +}) + +test('object array', () => { + const src = ` + class Foo { + value: integer + constructor(n) { this.value = n } + } + ` + const src2 = ` + const a = new Array(n) + ` + expect(() => compileAndRun(src + src2, destFile)).toThrow(/wrong number of arguments/) + + const src3 = ` + const a = new Array(n, null) + ` + expect(() => compileAndRun(src + src3, destFile)).toThrow(/incompatible argument/) + + const src5 = ` + function foo(n: integer) { + const obj = new Foo(7) + const a = new Array(n, obj) + return a + } + + const arr = foo(3) + print(arr[0].value) + + const v: any = arr + print(v[0].value) + + const arr2 = v + print(arr2[0].value) + + v[0].value = 13 + const arr2d = [ arr ] + print(arr2d[0]) + print(arr2d[0][0].value) + ` + expect(compileAndRun(src + src5, destFile)).toBe('7\n7\n7\n\n13\n') +}) + +test('array access in multiple source files', () => { + const src1 = ` + class Foo { + value: integer + constructor(x: integer) { this.value = x } + make(n) { return new Array(n, this) } + } + + function foo(a) { + return a[0] + } + ` + const src2 = ` + function bar() { + const a = new Foo(7).make(3) + print(typeof a) + const b: any = a + const aa: Foo[] = b + print(foo(aa)) + } + + bar() + ` + + expect(multiCompileAndRun(src1, src2, destFile)).toBe('Foo[]\n\n') +})