From 68401d9dc87d3817685273cf88c167879bef8486 Mon Sep 17 00:00:00 2001
From: fleur <contact@shib.cc>
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()