gen: add callconv attribute for fn and type (#14027)

pull/13758/merge
fleur 2022-04-14 10:29:52 +02:00 committed by GitHub
parent 5905590e78
commit 68401d9dc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 42 deletions

View File

@ -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 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). 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). [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. 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 nums = [] // The array is now empty
println(nums.len) // "0" 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. 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. 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. 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: the index as shown here:
```v ```v
@ -1022,7 +1022,7 @@ upper_fn := words.map(fn (w string) string {
println(upper_fn) // ['HELLO', 'WORLD'] 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. processed in filter/map methods.
Additionally, `.any()` and `.all()` can be used to conveniently test 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: There are further built-in methods for arrays:
* `a.repeat(n)` concatenates the array elements `n` times * `a.repeat(n)` concatenates the array elements `n` times
* `a.insert(i, val)` inserts a new element `val` at index `i` and * `a.insert(i, val)` inserts a new element `val` at index `i` and
shifts all following elements to the right shifts all following elements to the right
* `a.insert(i, [3, 4, 5])` inserts several elements * `a.insert(i, [3, 4, 5])` inserts several elements
* `a.prepend(val)` inserts a value at the beginning, equivalent to `a.insert(0, val)` * `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.pop()` removes the last element and returns it
* `a.reverse()` makes a new array with the elements of `a` in reverse order * `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.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 using `joiner` string as a separator
See also [vlib/arrays](https://modules.vlang.io/arrays.html). 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. V supports array and string slices with negative indexes.
Negative indexing starts from the end of the array towards the start, 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 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]`. 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 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 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 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()`. 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. targeted.
## Unions ## 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#. 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 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: the struct, and you do `a.speak()`, *only* the interface method is called:
```v ```v
@ -3491,13 +3491,13 @@ Above, `http.get` returns a `?http.Response`. `resp` is only in scope for the fi
## Custom error types ## Custom error types
V gives you the ability to define custom error types through the `IError` interface. 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 The interface requires two methods: `msg() string` and `code() int`. Every type that
implements these methods can be used as an error. implements these methods can be used as an error.
When defining a custom error type it is recommended to embed the builtin `Error` default 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, 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 so you only have to implement what you really need, and may provide additional utility
functions in the future. functions in the future.
```v ```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 _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._ 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 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 compile your long running processes with `-gc boehm`, which will use the
Boehm-Demers-Weiser conservative garbage collector, to free the memory, that your Boehm-Demers-Weiser conservative garbage collector, to free the memory, that your
programs leak, at runtime. 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`: at the top of all files in your module. For `mymodule.v`:
```v ```v
module mymodule module mymodule
pub fn hello_world() { pub fn hello_world() {
println('Hello World!') println('Hello World!')
} }
@ -5745,11 +5745,11 @@ fn main() {
``` ```
Build this example with `v -live message.v`. Build this example with `v -live message.v`.
You can also run this example with `v -live run 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, Make sure that in command you use a path to a V's file,
**not** a path to a folder (like `v -live run .`) - **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), 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. because changes in *message.v* will have no effect.
Functions that you want to be reloaded must have `[live]` attribute Functions that you want to be reloaded must have `[live]` attribute
@ -5978,10 +5978,15 @@ fn custom_allocations() {
struct C.Foo { struct C.Foo {
} }
// Used in Win32 API code when you need to pass callback function // Used to add a custom calling convention to a function, available calling convention: stdcall, fastcall and cdecl.
[windows_stdcall] // This list aslo apply for type aliases (see below).
[callconv: "stdcall"]
fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int) 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: // Windows only:
// If a default graphics library is imported (ex. gg, ui), then the graphical window takes // 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. // priority and no console window is created, effectively disabling println() statements.

View File

@ -1152,6 +1152,7 @@ pub:
pos token.Pos pos token.Pos
type_pos token.Pos type_pos token.Pos
comments []Comment comments []Comment
attrs []Attr // attributes of type declaration
} }
// TODO: handle this differently // TODO: handle this differently

View File

@ -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) { pub fn (mut f Fmt) fn_type_decl(node ast.FnTypeDecl) {
f.attrs(node.attrs)
if node.is_pub { if node.is_pub {
f.write('pub ') f.write('pub ')
} }

View File

@ -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
}

View File

@ -96,6 +96,7 @@ mut:
is_json_fn bool // inside json.encode() is_json_fn bool // inside json.encode()
is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)`
is_fn_index_call bool 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 vlines_path string // set to the proper path for generating #line directives
optionals map[string]string // to avoid duplicates optionals map[string]string // to avoid duplicates
done_optionals shared []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{} inner_loop: &ast.EmptyStmt{}
field_data_type: ast.Type(table.find_type_idx('FieldData')) field_data_type: ast.Type(table.find_type_idx('FieldData'))
init: strings.new_builder(100) init: strings.new_builder(100)
is_cc_msvc: pref.ccompiler == 'msvc'
} }
// anon fn may include assert and thus this needs // anon fn may include assert and thus this needs
// to be included before any test contents are written // 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 done_optionals: global_g.done_optionals
is_autofree: global_g.pref.autofree is_autofree: global_g.pref.autofree
referenced_fns: global_g.referenced_fns referenced_fns: global_g.referenced_fns
is_cc_msvc: global_g.is_cc_msvc
} }
g.gen_file() g.gen_file()
return g 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) if !info.has_decl && (not_anon || is_fn_sig) && !func.return_type.has_flag(.generic)
&& !has_generic_arg { && !has_generic_arg {
fn_name := sym.cname 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 { for i, param in func.params {
g.type_definitions.write_string(g.typ(param.typ)) g.type_definitions.write_string(g.typ(param.typ))
if i < func.params.len - 1 { if i < func.params.len - 1 {
g.type_definitions.write_string(',') g.type_definitions.write_string(',')
} }
} }
g.type_definitions.writeln(');') g.type_definitions.writeln(')$call_conv_attribute_suffix;')
} }
} }

View File

@ -2148,10 +2148,13 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string {
'windows_stdcall' { 'windows_stdcall' {
// windows attributes (msvc/mingw) // windows attributes (msvc/mingw)
// prefixed by windows to indicate they're for advanced users only and not really supported by V. // 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' { '_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' { 'console' {
g.force_main_console = true g.force_main_console = true
@ -2163,3 +2166,7 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string {
} }
return fn_attrs return fn_attrs
} }
fn call_convention_attribute(cconvention string, is_cc_msvc bool) string {
return if is_cc_msvc { '__$cconvention ' } else { '__attribute__(($cconvention)) ' }
}

View File

@ -185,18 +185,60 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
mut comments := []ast.Comment{} mut comments := []ast.Comment{}
for fna in p.attrs { for fna in p.attrs {
match fna.name { match fna.name {
'noreturn' { is_noreturn = true } 'noreturn' {
'manualfree' { is_manualfree = true } is_noreturn = true
'deprecated' { is_deprecated = true } }
'direct_array_access' { is_direct_arr = true } 'manualfree' {
'keep_args_alive' { is_keep_alive = true } is_manualfree = true
'export' { is_exported = true } }
'wasm_export' { is_exported = true } 'deprecated' {
'unsafe' { is_unsafe = true } is_deprecated = true
'trusted' { is_trusted = true } }
'c2v_variadic' { is_c2v_variadic = true } 'direct_array_access' {
'use_new' { is_ctor_new = true } is_direct_arr = true
'markused' { is_markused = 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 {} else {}
} }
} }

View File

@ -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 { pub fn (mut p Parser) parse_fn_type(name string) ast.Type {
// p.warn('parse fn') // p.warn('parse fn')
p.check(.key_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 mut has_generic := false
line_nr := p.tok.line_nr line_nr := p.tok.line_nr
args, _, is_variadic := p.fn_args() 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: return_type
return_type_pos: return_type_pos return_type_pos: return_type_pos
is_method: false is_method: false
attrs: p.attrs
} }
// MapFooFn typedefs are manually added in cheaders.v // MapFooFn typedefs are manually added in cheaders.v
// because typedefs get generated after the map struct is generated // because typedefs get generated after the map struct is generated

View File

@ -3579,6 +3579,8 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
p.table.sym(fn_type).is_pub = is_pub p.table.sym(fn_type).is_pub = is_pub
type_pos = type_pos.extend(p.tok.pos()) type_pos = type_pos.extend(p.tok.pos())
comments = p.eat_comments(same_line: true) comments = p.eat_comments(same_line: true)
attrs := p.attrs
p.attrs = []
return ast.FnTypeDecl{ return ast.FnTypeDecl{
name: fn_name name: fn_name
is_pub: is_pub is_pub: is_pub
@ -3586,6 +3588,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
pos: decl_pos pos: decl_pos
type_pos: type_pos type_pos: type_pos
comments: comments comments: comments
attrs: attrs
} }
} }
sum_variants << p.parse_sum_type_variants() sum_variants << p.parse_sum_type_variants()