From 68401d9dc87d3817685273cf88c167879bef8486 Mon Sep 17 00:00:00 2001 From: fleur Date: Thu, 14 Apr 2022 10:29:52 +0200 Subject: [PATCH] gen: add callconv attribute for fn and type (#14027) --- doc/docs.md | 57 ++++++++-------- vlib/v/ast/ast.v | 1 + vlib/v/fmt/fmt.v | 1 + ..._attributes_and_calling_convention_keep.vv | 10 +++ vlib/v/gen/c/cgen.v | 28 +++++++- vlib/v/gen/c/fn.v | 11 +++- vlib/v/parser/fn.v | 66 +++++++++++++++---- vlib/v/parser/parse_type.v | 18 +++++ vlib/v/parser/parser.v | 3 + 9 files changed, 153 insertions(+), 42 deletions(-) create mode 100644 vlib/v/fmt/tests/fn_type_attributes_and_calling_convention_keep.vv diff --git a/doc/docs.md b/doc/docs.md index 89cea74cbe..32e2db2e96 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -40,7 +40,7 @@ NB: You can also pass one of `-gcc`, `-msvc`, `-clang` to `make.bat` instead, if you do prefer to use a different C compiler, but -tcc is small, fast, and easy to install (V will download a prebuilt binary automatically). -For C compiler downloads and more info, see +For C compiler downloads and more info, see [here](https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows). It is recommended to add this folder to the PATH of your environment variables. @@ -843,7 +843,7 @@ println(nums.cap) // "3" or greater nums = [] // The array is now empty println(nums.len) // "0" ``` -`data` is a field (of type `voidptr`) with the address of the first +`data` is a field (of type `voidptr`) with the address of the first element. This is for low-level [`unsafe`](#memory-unsafe-code) code. Note that the fields are read-only and can't be modified by the user. @@ -892,7 +892,7 @@ for i in 0 .. 1000 { ``` Note: The above code uses a [range `for`](#range-for) statement. -You can initialize the array by accessing the `it` variable which gives +You can initialize the array by accessing the `it` variable which gives the index as shown here: ```v @@ -1022,7 +1022,7 @@ upper_fn := words.map(fn (w string) string { println(upper_fn) // ['HELLO', 'WORLD'] ``` -`it` is a builtin variable which refers to the element currently being +`it` is a builtin variable which refers to the element currently being processed in filter/map methods. Additionally, `.any()` and `.all()` can be used to conveniently test @@ -1035,8 +1035,8 @@ println(nums.all(it >= 2)) // false ``` There are further built-in methods for arrays: -* `a.repeat(n)` concatenates the array elements `n` times -* `a.insert(i, val)` inserts a new element `val` at index `i` and +* `a.repeat(n)` concatenates the array elements `n` times +* `a.insert(i, val)` inserts a new element `val` at index `i` and shifts all following elements to the right * `a.insert(i, [3, 4, 5])` inserts several elements * `a.prepend(val)` inserts a value at the beginning, equivalent to `a.insert(0, val)` @@ -1052,7 +1052,7 @@ There are further built-in methods for arrays: * `a.pop()` removes the last element and returns it * `a.reverse()` makes a new array with the elements of `a` in reverse order * `a.reverse_in_place()` reverses the order of elements in `a` -* `a.join(joiner)` concatenates an array of strings into one string +* `a.join(joiner)` concatenates an array of strings into one string using `joiner` string as a separator See also [vlib/arrays](https://modules.vlang.io/arrays.html). @@ -1185,7 +1185,7 @@ println(b) // [7, 3] V supports array and string slices with negative indexes. Negative indexing starts from the end of the array towards the start, -for example `-3` is equal to `array.len - 3`. +for example `-3` is equal to `array.len - 3`. Negative slices have a different syntax from normal slices, i.e. you need to add a `gate` between the array name and the square bracket: `a#[..-3]`. The `gate` specifies that this is a different type of slice and remember that @@ -2223,7 +2223,7 @@ button.Size = Size{ If multiple embedded structs have methods or fields with the same name, or if methods or fields with the same name are defined in the struct, you can call methods or assign to variables in the embedded struct like `button.Size.area()`. -When you do not specify the embedded struct name, the method of the outermost struct will be +When you do not specify the embedded struct name, the method of the outermost struct will be targeted. ## Unions @@ -2951,7 +2951,7 @@ a convenience for writing `s.xyz()` instead of `xyz(s)`. N.B. This feature is NOT a "default implementation" like in C#. For example, if a struct `cat` is wrapped in an interface `a`, that has -implemented a method with the same name `speak`, as a method implemented by +implemented a method with the same name `speak`, as a method implemented by the struct, and you do `a.speak()`, *only* the interface method is called: ```v @@ -3491,13 +3491,13 @@ Above, `http.get` returns a `?http.Response`. `resp` is only in scope for the fi ## Custom error types -V gives you the ability to define custom error types through the `IError` interface. -The interface requires two methods: `msg() string` and `code() int`. Every type that -implements these methods can be used as an error. +V gives you the ability to define custom error types through the `IError` interface. +The interface requires two methods: `msg() string` and `code() int`. Every type that +implements these methods can be used as an error. -When defining a custom error type it is recommended to embed the builtin `Error` default -implementation. This provides an empty default implementation for both required methods, -so you only have to implement what you really need, and may provide additional utility +When defining a custom error type it is recommended to embed the builtin `Error` default +implementation. This provides an empty default implementation for both required methods, +so you only have to implement what you really need, and may provide additional utility functions in the future. ```v @@ -4112,8 +4112,8 @@ memory manually. (See [attributes](#attributes)). _Note: right now autofree is hidden behind the -autofree flag. It will be enabled by default in V 0.3. If autofree is not used, V programs will leak memory._ -Note 2: Autofree is still WIP. Until it stabilises and becomes the default, please -compile your long running processes with `-gc boehm`, which will use the +Note 2: Autofree is still WIP. Until it stabilises and becomes the default, please +compile your long running processes with `-gc boehm`, which will use the Boehm-Demers-Weiser conservative garbage collector, to free the memory, that your programs leak, at runtime. @@ -4702,7 +4702,7 @@ Modules are up to date. at the top of all files in your module. For `mymodule.v`: ```v module mymodule - + pub fn hello_world() { println('Hello World!') } @@ -5745,11 +5745,11 @@ fn main() { ``` Build this example with `v -live message.v`. - -You can also run this example with `v -live run message.v`. - Make sure that in command you use a path to a V's file, - **not** a path to a folder (like `v -live run .`) - - in that case you need to modify content of a folder (add new file, for example), + +You can also run this example with `v -live run message.v`. + Make sure that in command you use a path to a V's file, + **not** a path to a folder (like `v -live run .`) - + in that case you need to modify content of a folder (add new file, for example), because changes in *message.v* will have no effect. Functions that you want to be reloaded must have `[live]` attribute @@ -5978,10 +5978,15 @@ fn custom_allocations() { struct C.Foo { } -// Used in Win32 API code when you need to pass callback function -[windows_stdcall] +// Used to add a custom calling convention to a function, available calling convention: stdcall, fastcall and cdecl. +// This list aslo apply for type aliases (see below). +[callconv: "stdcall"] fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int) +// Used to add a custom calling convention to a function type aliases. +[callconv: "fastcall"] +type FastFn = fn (int) bool + // Windows only: // If a default graphics library is imported (ex. gg, ui), then the graphical window takes // priority and no console window is created, effectively disabling println() statements. diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 9f5e4fe596..25844c74b5 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1152,6 +1152,7 @@ pub: pos token.Pos type_pos token.Pos comments []Comment + attrs []Attr // attributes of type declaration } // TODO: handle this differently diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 3d6ef34dc8..5430241a55 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1290,6 +1290,7 @@ pub fn (mut f Fmt) alias_type_decl(node ast.AliasTypeDecl) { } pub fn (mut f Fmt) fn_type_decl(node ast.FnTypeDecl) { + f.attrs(node.attrs) if node.is_pub { f.write('pub ') } diff --git a/vlib/v/fmt/tests/fn_type_attributes_and_calling_convention_keep.vv b/vlib/v/fmt/tests/fn_type_attributes_and_calling_convention_keep.vv new file mode 100644 index 0000000000..4012843980 --- /dev/null +++ b/vlib/v/fmt/tests/fn_type_attributes_and_calling_convention_keep.vv @@ -0,0 +1,10 @@ +[callconv: 'stdcall'] +fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int) + +[callconv: 'stdcall'] +type FastFn = fn (int) bool + +[callconv: 'fastcall'] +fn zzz(x int, y int) int { + return x + y * 2 +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 8d6269015f..85c26ac2ec 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -96,6 +96,7 @@ mut: is_json_fn bool // inside json.encode() is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` is_fn_index_call bool + is_cc_msvc bool // g.pref.ccompiler == 'msvc' vlines_path string // set to the proper path for generating #line directives optionals map[string]string // to avoid duplicates done_optionals shared []string // to avoid duplicates @@ -258,6 +259,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { inner_loop: &ast.EmptyStmt{} field_data_type: ast.Type(table.find_type_idx('FieldData')) init: strings.new_builder(100) + is_cc_msvc: pref.ccompiler == 'msvc' } // anon fn may include assert and thus this needs // to be included before any test contents are written @@ -557,6 +559,7 @@ fn cgen_process_one_file_cb(p &pool.PoolProcessor, idx int, wid int) &Gen { done_optionals: global_g.done_optionals is_autofree: global_g.pref.autofree referenced_fns: global_g.referenced_fns + is_cc_msvc: global_g.is_cc_msvc } g.gen_file() return g @@ -1343,14 +1346,35 @@ pub fn (mut g Gen) write_fn_typesymbol_declaration(sym ast.TypeSymbol) { if !info.has_decl && (not_anon || is_fn_sig) && !func.return_type.has_flag(.generic) && !has_generic_arg { fn_name := sym.cname - g.type_definitions.write_string('typedef ${g.typ(func.return_type)} (*$fn_name)(') + + mut call_conv := '' + mut msvc_call_conv := '' + for attr in func.attrs { + match attr.name { + 'callconv' { + if g.is_cc_msvc { + msvc_call_conv = '__$attr.arg ' + } else { + call_conv = '$attr.arg' + } + } + else {} + } + } + call_conv_attribute_suffix := if call_conv.len != 0 { + '__attribute__(($call_conv))' + } else { + '' + } + + g.type_definitions.write_string('typedef ${g.typ(func.return_type)} ($msvc_call_conv*$fn_name)(') for i, param in func.params { g.type_definitions.write_string(g.typ(param.typ)) if i < func.params.len - 1 { g.type_definitions.write_string(',') } } - g.type_definitions.writeln(');') + g.type_definitions.writeln(')$call_conv_attribute_suffix;') } } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index e55383c288..4f67dd8ce1 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2148,10 +2148,13 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string { 'windows_stdcall' { // windows attributes (msvc/mingw) // prefixed by windows to indicate they're for advanced users only and not really supported by V. - fn_attrs += '__stdcall ' + fn_attrs += call_convention_attribute('stdcall', g.is_cc_msvc) } '_fastcall' { - fn_attrs += '__fastcall ' + fn_attrs += call_convention_attribute('fastcall', g.is_cc_msvc) + } + 'callconv' { + fn_attrs += call_convention_attribute(attr.arg, g.is_cc_msvc) } 'console' { g.force_main_console = true @@ -2163,3 +2166,7 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string { } return fn_attrs } + +fn call_convention_attribute(cconvention string, is_cc_msvc bool) string { + return if is_cc_msvc { '__$cconvention ' } else { '__attribute__(($cconvention)) ' } +} diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index e956f2e116..4cff8699f6 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -185,18 +185,60 @@ fn (mut p Parser) fn_decl() ast.FnDecl { mut comments := []ast.Comment{} for fna in p.attrs { match fna.name { - 'noreturn' { is_noreturn = true } - 'manualfree' { is_manualfree = true } - 'deprecated' { is_deprecated = true } - 'direct_array_access' { is_direct_arr = true } - 'keep_args_alive' { is_keep_alive = true } - 'export' { is_exported = true } - 'wasm_export' { is_exported = true } - 'unsafe' { is_unsafe = true } - 'trusted' { is_trusted = true } - 'c2v_variadic' { is_c2v_variadic = true } - 'use_new' { is_ctor_new = true } - 'markused' { is_markused = true } + 'noreturn' { + is_noreturn = true + } + 'manualfree' { + is_manualfree = true + } + 'deprecated' { + is_deprecated = true + } + 'direct_array_access' { + is_direct_arr = true + } + 'keep_args_alive' { + is_keep_alive = true + } + 'export' { + is_exported = true + } + 'wasm_export' { + is_exported = true + } + 'unsafe' { + is_unsafe = true + } + 'trusted' { + is_trusted = true + } + 'c2v_variadic' { + is_c2v_variadic = true + } + 'use_new' { + is_ctor_new = true + } + 'markused' { + is_markused = true + } + 'windows_stdcall' { + // TODO: uncomment after bootstrapping and replacing usages in builtin + // p.note_with_pos('windows_stdcall has been deprecated, it will be an error soon', p.tok.pos()) + } + '_fastcall' { + // TODO: uncomment after bootstrapping and replacing usages in builtin + // p.note_with_pos('_fastcall has been deprecated, it will be an error soon', p.tok.pos()) + } + 'callconv' { + if !fna.has_arg { + p.error_with_pos('callconv attribute is present but its value is missing', + p.prev_tok.pos()) + } + if fna.arg !in ['stdcall', 'fastcall', 'cdecl'] { + p.error_with_pos('unsupported calling convention, supported are stdcall, fastcall and cdecl', + p.prev_tok.pos()) + } + } else {} } } diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index a4b769c952..ee42932972 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -229,6 +229,23 @@ pub fn (mut p Parser) parse_multi_return_type() ast.Type { pub fn (mut p Parser) parse_fn_type(name string) ast.Type { // p.warn('parse fn') p.check(.key_fn) + + for attr in p.attrs { + match attr.name { + 'callconv' { + if !attr.has_arg { + p.error_with_pos('callconv attribute is present but its value is missing', + p.prev_tok.pos()) + } + if attr.arg !in ['stdcall', 'fastcall', 'cdecl'] { + p.error_with_pos('unsupported calling convention, supported are stdcall, fastcall and cdecl', + p.prev_tok.pos()) + } + } + else {} + } + } + mut has_generic := false line_nr := p.tok.line_nr args, _, is_variadic := p.fn_args() @@ -255,6 +272,7 @@ pub fn (mut p Parser) parse_fn_type(name string) ast.Type { return_type: return_type return_type_pos: return_type_pos is_method: false + attrs: p.attrs } // MapFooFn typedefs are manually added in cheaders.v // because typedefs get generated after the map struct is generated diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 964411f487..a12e12a726 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -3579,6 +3579,8 @@ fn (mut p Parser) type_decl() ast.TypeDecl { p.table.sym(fn_type).is_pub = is_pub type_pos = type_pos.extend(p.tok.pos()) comments = p.eat_comments(same_line: true) + attrs := p.attrs + p.attrs = [] return ast.FnTypeDecl{ name: fn_name is_pub: is_pub @@ -3586,6 +3588,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { pos: decl_pos type_pos: type_pos comments: comments + attrs: attrs } } sum_variants << p.parse_sum_type_variants()