From 84b99ceeb275628f96e03b274109d1f51fe612cf Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Wed, 25 Jan 2023 17:01:22 -0300 Subject: [PATCH] vlib: add a `v.reflection` module for reflection done at runtime (#17072) --- vlib/v/gen/c/cgen.v | 34 +++++- vlib/v/gen/c/fn.v | 1 + vlib/v/gen/c/reflection.v | 160 +++++++++++++++++++++++++++ vlib/v/reflection/reflection.v | 194 +++++++++++++++++++++++++++++++++ vlib/v/tests/reflection_test.v | 88 +++++++++++++++ 5 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 vlib/v/gen/c/reflection.v create mode 100644 vlib/v/reflection/reflection.v create mode 100644 vlib/v/tests/reflection_test.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index b96fa36d9e..ae527cae15 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -236,6 +236,12 @@ mut: // out_idx int out_fn_start_pos []int // for generating multiple .c files, stores locations of all fn positions in `out` string builder static_modifier string // for parallel_cc + + has_reflection bool + // reflection metadata initialization + reflection_funcs strings.Builder + reflection_others strings.Builder + reflection_strings &map[string]int } // global or const variable definition string @@ -264,6 +270,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string, $if time_cgening ? { timers_should_print = true } + mut reflection_strings := map[string]int{} mut global_g := Gen{ file: 0 out: strings.new_builder(512000) @@ -306,7 +313,12 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string, use_segfault_handler: !('no_segfault_handler' in pref.compile_defines || pref.os in [.wasm32, .wasm32_emscripten]) static_modifier: if pref.parallel_cc { 'static' } else { '' } + has_reflection: 'v.reflection' in table.modules + reflection_funcs: strings.new_builder(100) + reflection_others: strings.new_builder(100) + reflection_strings: &reflection_strings } + /* global_g.out_parallel = []strings.Builder{len: nr_cpus} for i in 0 .. nr_cpus { @@ -358,6 +370,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string, global_g.embedded_data.write(g.embedded_data) or { panic(err) } global_g.shared_types.write(g.shared_types) or { panic(err) } global_g.shared_functions.write(g.channel_definitions) or { panic(err) } + global_g.reflection_funcs.write(g.reflection_funcs) or { panic(err) } global_g.force_main_console = global_g.force_main_console || g.force_main_console @@ -455,6 +468,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string, mut g := global_g util.timing_start('cgen common') + // to make sure type idx's are the same in cached mods if g.pref.build_mode == .build_module { for idx, sym in g.table.type_symbols { @@ -664,6 +678,10 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen { referenced_fns: global_g.referenced_fns is_cc_msvc: global_g.is_cc_msvc use_segfault_handler: global_g.use_segfault_handler + has_reflection: 'v.reflection' in global_g.table.modules + reflection_funcs: strings.new_builder(100) + reflection_others: strings.new_builder(100) + reflection_strings: global_g.reflection_strings } g.gen_file() return g @@ -715,7 +733,6 @@ pub fn (mut g Gen) gen_file() { g.is_vlines_enabled = true g.inside_ternary = 0 } - g.stmts(g.file.stmts) // Transfer embedded files for path in g.file.embedded_files { @@ -5277,6 +5294,7 @@ fn (mut g Gen) write_init_function() { // ___argv is declared as voidptr here, because that unifies the windows/unix logic g.writeln('void _vinit(int ___argc, voidptr ___argv) {') + if g.pref.trace_calls { g.writeln('\tv__trace_calls__on_call(_SLIT("_vinit"));') } @@ -5302,7 +5320,20 @@ fn (mut g Gen) write_init_function() { if g.nr_closures > 0 { g.writeln('\t_closure_mtx_init();') } + + // reflection bootstraping + if g.has_reflection { + if var := g.global_const_defs['g_reflection'] { + g.writeln(var.init) + g.gen_reflection_data() + } + } + for mod_name in g.table.modules { + if g.has_reflection && mod_name == 'v.reflection' { + // ignore v.reflection already initialized above + continue + } mut is_empty := true // write globals and consts init later for var_name in g.sorted_global_const_names { @@ -5328,6 +5359,7 @@ fn (mut g Gen) write_init_function() { } } } + g.writeln('}') if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list { println(g.out.after(fn_vinit_start_pos)) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 07d4c9d64c..44cd1ee0e3 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -288,6 +288,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { } fn_header := '${visibility_kw}${type_name} ${fn_attrs}${name}(' g.definitions.write_string(fn_header) + g.gen_reflection_function(node) g.write(fn_header) } arg_start_pos := g.out.len diff --git a/vlib/v/gen/c/reflection.v b/vlib/v/gen/c/reflection.v new file mode 100644 index 0000000000..ad8ba6a77e --- /dev/null +++ b/vlib/v/gen/c/reflection.v @@ -0,0 +1,160 @@ +module c + +import v.ast +import v.util + +// reflection_string maps string to its idx +fn (mut g Gen) reflection_string(str string) int { + return unsafe { + g.reflection_strings[str] or { + g.reflection_strings[str] = g.reflection_strings.len + g.reflection_strings.len - 1 + } + } +} + +// gen_reflection_strings generates the reflectino string registration +[inline] +fn (mut g Gen) gen_reflection_strings() { + for str, idx in g.reflection_strings { + g.reflection_others.write_string('\tv__reflection__add_string(_SLIT("${str}"), ${idx});\n') + } +} + +// gen_empty_array generates code for empty array +[inline] +fn (g Gen) gen_empty_array(type_name string) string { + return '__new_array_with_default(0, 0, sizeof(${type_name}), 0)' +} + +// gen_functionarg_array generates the code for functionarg argument +[inline] +fn (g Gen) gen_functionarg_array(type_name string, node ast.FnDecl) string { + if node.params.len == 0 { + return g.gen_empty_array(type_name) + } + mut out := 'new_array_from_c_array(${node.params.len},${node.params.len},sizeof(${type_name}),' + out += '_MOV((${type_name}[${node.params.len}]){' + for param in node.params { + out += '((${type_name}){.name=_SLIT("${param.name}"),.typ=${param.typ.idx()},}),' + } + out += '}))' + return out +} + +// gen_functionarg_array generates the code for functionarg argument +[inline] +fn (mut g Gen) gen_function_array(nodes []ast.FnDecl) string { + type_name := 'v__reflection__Function' + + if nodes.len == 0 { + return g.gen_empty_array(type_name) + } + + mut out := 'new_array_from_c_array(${nodes.len},${nodes.len},sizeof(${type_name}),' + out += '_MOV((${type_name}[${nodes.len}]){' + for method in nodes { + out += g.gen_reflection_fndecl(method) + out += ',' + } + out += '}))' + return out +} + +// gen_reflection_enum_fields generates C code for enum fields +[inline] +fn (g Gen) gen_reflection_enum_fields(fields []ast.EnumField) string { + if fields.len == 0 { + return g.gen_empty_array('v__reflection__EnumField') + } + mut out := 'new_array_from_c_array(${fields.len},${fields.len},sizeof(v__reflection__EnumField),' + out += '_MOV((v__reflection__EnumField[${fields.len}]){' + for field in fields { + out += '((v__reflection__EnumField){.name=_SLIT("${field.name}")}),' + } + out += '}))' + return out +} + +// gen_reflection_fndecl generates C code for function declaration +[inline] +fn (mut g Gen) gen_reflection_fndecl(node ast.FnDecl) string { + mut arg_str := '((v__reflection__Function){' + v_name := node.name.all_after_last('.') + arg_str += '.mod_name=_SLIT("${node.mod}"),' + arg_str += '.name=_SLIT("${v_name}"),' + arg_str += '.args=${g.gen_functionarg_array('v__reflection__FunctionArg', node)},' + arg_str += '.file_idx=${g.reflection_string(util.cescaped_path(node.file))},' + arg_str += '.line_start=${node.pos.line_nr},' + arg_str += '.line_end=${node.pos.last_line},' + arg_str += '.is_variadic=${node.is_variadic},' + arg_str += '.return_typ=${node.return_type.idx()},' + arg_str += '.receiver_typ=${node.receiver.typ.idx()}' + arg_str += '})' + return arg_str +} + +// gen_reflection_sym generates C code for TypeSymbol struct +[inline] +fn (g Gen) gen_reflection_sym(tsym ast.TypeSymbol) string { + kind_name := if tsym.kind in [.none_, .struct_, .enum_, .interface_] { + tsym.kind.str() + '_' + } else { + tsym.kind.str() + } + return '(v__reflection__TypeSymbol){.name=_SLIT("${tsym.name}"),.idx=${tsym.idx},.parent_idx=${tsym.parent_idx},.language=_SLIT("${tsym.language}"),.kind=v__ast__Kind__${kind_name}}' +} + +// gen_reflection_function generates C code for reflection function metadata +[inline] +fn (mut g Gen) gen_reflection_function(node ast.FnDecl) { + if !g.has_reflection { + return + } + func_struct := g.gen_reflection_fndecl(node) + g.reflection_funcs.write_string('\tv__reflection__add_func(${func_struct});\n') +} + +// gen_reflection_data generates code to initilized V reflection metadata +fn (mut g Gen) gen_reflection_data() { + // modules declaration + for mod_name in g.table.modules { + g.reflection_others.write_string('\tv__reflection__add_module(_SLIT("${mod_name}"));\n') + } + + // enum declaration + for full_name, enum_ in g.table.enum_decls { + name := full_name.all_after_last('.') + fields := g.gen_reflection_enum_fields(enum_.fields) + g.reflection_others.write_string('\tv__reflection__add_enum((v__reflection__Enum){.name=_SLIT("${name}"),.is_pub=${enum_.is_pub},.is_flag=${enum_.is_flag},.typ=${enum_.typ.idx()},.line_start=${enum_.pos.line_nr},.line_end=${enum_.pos.last_line},.fields=${fields}});\n') + } + + // types declaration + for full_name, idx in g.table.type_idxs { + tsym := g.table.sym_by_idx(idx) + name := full_name.all_after_last('.') + sym := g.gen_reflection_sym(tsym) + g.reflection_others.write_string('\tv__reflection__add_type((v__reflection__Type){.name=_SLIT("${name}"),.idx=${idx},.sym=${sym}});\n') + } + + // interface declaration + for _, idecl in g.table.interfaces { + name := idecl.name.all_after_last('.') + methods := g.gen_function_array(idecl.methods) + g.reflection_others.write_string('\tv__reflection__add_interface((v__reflection__Interface){.name=_SLIT("${name}"),.typ=${idecl.typ.idx()},.is_pub=${idecl.is_pub},.methods=${methods}});\n') + } + + // type symbols declaration + for _, tsym in g.table.type_symbols { + sym := g.gen_reflection_sym(tsym) + g.reflection_others.write_string('\tv__reflection__add_type_symbol(${sym});\n') + } + + g.gen_reflection_strings() + + // funcs meta info filling + g.writeln(g.reflection_funcs.str()) + + // others meta info filling + g.writeln(g.reflection_others.str()) +} diff --git a/vlib/v/reflection/reflection.v b/vlib/v/reflection/reflection.v new file mode 100644 index 0000000000..d3a6096da2 --- /dev/null +++ b/vlib/v/reflection/reflection.v @@ -0,0 +1,194 @@ +[has_globals] +module reflection + +import v.ast + +__global g_reflection = Reflection{} + +[heap; minify] +pub struct Reflection { +pub mut: + modules []Module + funcs []Function + types []Type + type_symbols []TypeSymbol + enums []Enum + interfaces []Interface + strings map[int]string +} + +pub struct Interface { +pub: + name string // interface name + typ int // type idx + is_pub bool // is pub? + methods []Function // methods +} + +pub struct EnumField { +pub: + name string // field name +} + +pub struct Enum { +pub: + name string // enum name + is_pub bool // is pub? + is_flag bool // is flag? + typ int // type idx + line_start int // decl start line + line_end int // decl end line + fields []EnumField // enum fields +} + +pub struct TypeSymbol { +pub: + name string // symbol name + idx int // symbol idx + parent_idx int // symbol parent idx + language string // language + kind ast.Kind // kind +} + +pub struct Type { +pub: + name string // type name + idx int // type idx + sym TypeSymbol // type symbol +} + +pub struct Module { +pub: + name string // module name +} + +pub struct FunctionArg { +pub: + name string // argument name + typ int // argument type idx +} + +pub struct Function { +pub: + mod_name string // module name + name string // function/method name + args []FunctionArg // function/method args + file_idx int // source file name + line_start int // decl start line + line_end int // decl end line + is_variadic bool // is variadic? + return_typ int // return type idx + receiver_typ int // receiver type idx (is a method) +} + +// API module + +pub fn get_string_by_idx(idx int) string { + return g_reflection.strings[idx] +} + +// type_of returns the type info of the passed value +pub fn type_of[T](val T) Type { + return g_reflection.types.filter(it.idx == typeof[T]().idx)[0] +} + +// get_modules returns the module name built with V source +pub fn get_modules() []Module { + return g_reflection.modules +} + +// get_functions returns the functions built with V source +pub fn get_funcs() []Function { + return g_reflection.funcs +} + +// get_types returns the registered types +pub fn get_types() []Type { + return g_reflection.types +} + +// get_enums returns the registered enums +pub fn get_enums() []Enum { + return g_reflection.enums +} + +// get_aliases returns the registered aliases +pub fn get_aliases() []Type { + alias_idxs := g_reflection.type_symbols.filter(it.kind == .alias).map(it.idx) + return g_reflection.types.filter(it.idx in alias_idxs) +} + +// get_interfaces returns the registered aliases +pub fn get_interfaces() []Interface { + return g_reflection.interfaces +} + +// get_sum_types returns the registered sum types +pub fn get_sum_types() []Type { + sumtype_idxs := g_reflection.type_symbols.filter(it.kind == .sum_type).map(it.idx) + return g_reflection.types.filter(it.idx in sumtype_idxs) +} + +// get_type_symbol returns the registered type symbols +pub fn get_type_symbols() []TypeSymbol { + return g_reflection.type_symbols +} + +// Type API +pub fn type_name(idx int) string { + t := g_reflection.types.filter(it.idx == idx) + return if t.len != 0 { t[0].name } else { '' } +} + +pub fn get_type(idx int) ?Type { + t := g_reflection.types.filter(it.idx == idx) + return if t.len != 0 { t[0] } else { none } +} + +// Type Symbol API +pub fn type_symbol_name(idx int) string { + t := g_reflection.type_symbols.filter(it.idx == idx) + return if t.len != 0 { t[0].name } else { '' } +} + +pub fn get_type_symbol(idx int) ?TypeSymbol { + t := g_reflection.type_symbols.filter(it.idx == idx) + return if t.len != 0 { t[0] } else { none } +} + +// V reflection metainfo API (called from backend to fill metadata info) + +[markused] +fn add_module(mod_name string) { + g_reflection.modules << Module{mod_name} +} + +[markused] +fn add_func(func Function) { + g_reflection.funcs << func +} + +[markused] +fn add_type(type_ Type) { + g_reflection.types << type_ +} + +[markused] +fn add_type_symbol(typesymbol TypeSymbol) { + g_reflection.type_symbols << typesymbol +} + +[markused] +fn add_enum(enum_ Enum) { + g_reflection.enums << enum_ +} + +[markused] +fn add_interface(interface_ Interface) { + g_reflection.interfaces << interface_ +} + +[markused] +fn add_string(str string, idx int) { + g_reflection.strings[idx] = str +} diff --git a/vlib/v/tests/reflection_test.v b/vlib/v/tests/reflection_test.v new file mode 100644 index 0000000000..01928bb326 --- /dev/null +++ b/vlib/v/tests/reflection_test.v @@ -0,0 +1,88 @@ +import v.reflection + +type MyInt = int + +type MySumType = f64 | int + +enum TestEnum { + foo + bar +} + +struct User { + name string +} + +fn (u User) get_name() string { + return u.name +} + +fn test2(arg []string) {} + +[noreturn] +fn test3(a reflection.Function) { +} + +fn test_module_existing() { + assert 'v.reflection' in reflection.get_modules().map(it.name) +} + +fn test_func_attribute() { + assert reflection.get_funcs().filter(it.name == 'test3')[0].is_variadic == false +} + +fn test_func_name() { + assert reflection.get_funcs().filter(it.name == 'test2')[0].name == 'test2' +} + +fn test_type_name() { + ret_typ := reflection.get_funcs().filter(it.name == 'test3')[0].return_typ + assert reflection.type_name(ret_typ) == 'void' + assert reflection.get_type(ret_typ)?.name == 'void' + assert reflection.get_type_symbol(ret_typ)?.name == 'void' + assert reflection.type_name(reflection.get_funcs().filter(it.name == 'test3')[0].args[0].typ) == 'Function' +} + +fn test_type_symbol() { + ret_typ := reflection.get_funcs().filter(it.name == 'test3')[0].return_typ + assert reflection.get_type_symbol(ret_typ)?.language == 'v' +} + +fn test_method() { + method := reflection.get_funcs().filter(it.name == 'get_name')[0] + assert reflection.type_name(method.return_typ) == 'string' + println(reflection.get_type(method.receiver_typ)?.name) + assert reflection.get_type(method.receiver_typ)?.name == 'User' +} + +fn test_enum() { + assert reflection.get_enums().filter(it.name == 'TestEnum')[0].name == 'TestEnum' +} + +fn test_get_aliases() { + assert reflection.get_aliases().filter(it.name == 'MyInt')[0].name == 'MyInt' +} + +fn test_get_sum_types() { + assert reflection.get_sum_types().filter(it.name == 'MySumType')[0].name == 'MySumType' +} + +fn test_get_interfaces() { + assert reflection.get_interfaces().filter(it.name == 'IError')[0].name == 'IError' +} + +fn test_interfaces() { + assert reflection.get_interfaces().filter(it.name == 'IError')[0].methods.len == 2 +} + +fn test_enum_fields() { + assert reflection.get_enums().filter(it.name == 'TestEnum')[0].fields.map(it.name) == [ + 'foo', + 'bar', + ] +} + +fn test_get_string_by_idx() { + file_idx := reflection.get_funcs().filter(it.name == 'all_after_last')[0].file_idx + assert reflection.get_string_by_idx(file_idx).ends_with('string.v') +}