Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions vlib/builtin/backtraces_windows.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool {
}
for i in 0 .. frames {
frame_addr := backtraces[i]
if C.SymFromAddr(handle, frame_addr, &offset, si) == 1 {
if C.SymFromAddr(handle, u64(frame_addr), &offset, si) == 1 {
nframe := frames - i - 1
mut lineinfo := ''
if C.SymGetLineFromAddr64(handle, frame_addr, &offset, &sline64) == 1 {
if C.SymGetLineFromAddr64(handle, u64(frame_addr), &offset, &sline64) == 1 {
file_name := unsafe { tos3(sline64.f_file_name) }
lnumber := sline64.f_line_number
lineinfo = file_name + ':' + i64(lnumber).str()
Expand Down
39 changes: 37 additions & 2 deletions vlib/v/driver/driver.v
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,19 @@ fn compile_cached_c_source_object(obj_path string, source_file string, source_la
if language.len > 0 {
args << ['-x', language]
}
if effective_c_compiler_name(compiler, target) == 'msvc' {
// `cl` cannot list a source's dependencies like `-M` does, so the object cannot
// be validated against its headers later. Build it for this compilation only.
msvc_obj := os.join_path(uncached_dir, '${os.file_name(obj_path).all_before_last('.')}_${tempname.unique_token()}.obj')
args << ['-o', msvc_obj, '-c', source_file]
res := cmdexec.run(compiler, msvc_cl_args(args, target.os))
if res.exit_code != 0 {
os.rm(msvc_obj) or {}
return error('failed to build C object ${obj_path} from ${source_file}:\n${res.output}')
}
stats.temporary_objects << msvc_obj
return msvc_obj
}
manifest_path := c_object_manifest_path(cache_dir, obj_path, compiler, args, target, mut stats)
if cached_obj := valid_c_object_manifest(manifest_path, mut stats) {
return cached_obj
Expand Down Expand Up @@ -2835,6 +2848,10 @@ fn v3_c_compiler_flag_plan(options V3CCompilerFlagOptions) V3CCompilerFlagPlan {
before_inputs << options.pic_flag
}
before_inputs << v3_windows_executable_linker_flags(options.target_os, options.c_compiler, options.is_shared, options.is_o, options.subsystem, options.windows_gui_app)
if options.c_compiler == 'msvc' {
before_inputs << v3_msvc_link_flags(options.target_os, options.is_shared, options.is_o,
options.subsystem, options.windows_gui_app)
}
mut tcc_includes := ''
if options.is_tcc {
tcc_resources := v3_tcc_resource_flags(options.vroot)
Expand Down Expand Up @@ -3770,6 +3787,10 @@ fn c_typedef_is_function_pointer(source string, name string) bool {
}

fn cache_c_compiler_predefined_macros(flags []string, ccompiler string, target pref.Target, native_inputs_language string) (map[string]string, bool) {
if effective_c_compiler_name(ccompiler, target) == 'msvc' {
// `cl` has no `-dM` equivalent.
return map[string]string{}, false
}
path := os.join_path(os.vtmp_dir(), 'v3_compiler_macros_${tempname.unique_token()}.c')
defer {
os.rm(path) or {}
Expand Down Expand Up @@ -10509,7 +10530,7 @@ pub fn run(args []string) {
minimal_literal_output := !is_prof && !is_trace_calls
&& input_uses_minimal_literal_output_builtin(input_file, prefs, is_test_command, is_checker_fixture)
mut use_parallel_c_compilation := parallel_cc && backend == 'c' && !c_only && !effective_tcc
&& !is_o && coverage_dir.len == 0 && profile_file.len == 0
&& effective_c_compiler != 'msvc' && !is_o && coverage_dir.len == 0 && profile_file.len == 0
&& !is_trace_calls
&& v3_parallel_cc_monolithic_define !in user_defines
// `-keepc` and explicit `-b c` promise a complete generated C translation unit.
Expand Down Expand Up @@ -12617,6 +12638,17 @@ pub fn run(args []string) {
} else {
b.step_parallel('cgen', cgen_was_parallel)
}
if effective_c_compiler == 'msvc' && !cache_state.manager.enabled && !c_to_stdout {
msvc_lower_c_file(cc_src) or {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Lower typeof before invoking MSVC

When a program calls .pointers() on a fixed array, gen_fixed_array_pointers_expr emits a GNU-style typeof(...) declaration (vlib/v/gen/c/array.v:975), but this compatibility pass leaves that token unchanged. The resulting source is then compiled with /std:c11; Microsoft documents C typeof as requiring /std:clatest, so these otherwise valid V programs fail under -cc msvc. Add an MSVC-specific declaration in cgen or teach this pass to replace the emitted typeof.

Useful? React with 👍 / 👎.

eprintln('error preparing the generated C source for MSVC: ${err.msg()}')
cleanup_c_build_dir(cc_dir)
exit(1)
}
b.step('MSVC C compatibility')
}
if effective_c_compiler == 'msvc' && !c_only {
msvc_require_cl(c_compiler, host_os)
}
pic_flag := shared_pic_flag(is_shared || use_cached_dev_dylib, prefs.normalized_target_os())
mut linux_cross_sysroot := ''
if macos_linux_cross_compile && !c_only {
Expand Down Expand Up @@ -13375,7 +13407,10 @@ pub fn run(args []string) {
&& fallback_source == 'src.c' {
result = compile_v3_parallel_c(cc_src, c_compiler, &c_flag_plan, &large_c_flag_plan, native_support_inputs, cached_objects, cached_dev_dylib, needs_objective_c, cc_dir, cc_output_name, verbose || show_cc, parallel_c_job_count, parallel_c_unit_count, is_shared)
} else {
cc_args := c_flag_plan.compiler_args(cc_output_name, compiler_inputs, [])
mut cc_args := c_flag_plan.compiler_args(cc_output_name, compiler_inputs, [])
if effective_c_compiler == 'msvc' {
cc_args = msvc_cl_args(cc_args, prefs.normalized_target_os())
}
if verbose || show_cc {
println(' > ${cmdexec.display(c_compiler, cc_args)}')
}
Expand Down
292 changes: 292 additions & 0 deletions vlib/v/driver/msvc.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
module driver

import os
import v.gen.c as cgen
import v.pref

// MSVC's `cl` does not accept the gcc-style command lines that the rest of the driver
// builds. msvc_cl_args translates them, and msvc_lower_c_file makes the generated C
// itself acceptable to `cl` (see cgen.msvc_compat_c_source).

const msvc_default_libs = ['kernel32.lib', 'user32.lib', 'advapi32.lib', 'ws2_32.lib']

// Libraries that gcc-style toolchains link separately, but that are part of the MSVC
// C runtime or have no Windows counterpart.
const msvc_ignored_libs = ['m', 'pthread', 'dl', 'rt', 'c', 'gcc', 'gcc_s', 'stdc++', 'mingw32',
'mingwex', 'moldname', 'msvcrt', 'ucrt']

// Options that take the following argument as their operand and have no MSVC meaning.
const msvc_ignored_options_with_operand = ['-arch', '-target', '-isysroot', '--sysroot', '-MT',
'-MF', '-MQ', '-framework']

const msvc_source_extensions = ['.c', '.cc', '.cpp', '.cxx']

const msvc_linker_input_extensions = ['.o', '.obj', '.lib', '.a', '.res', '.def']

// v3_msvc_link_flags returns the linker options for a Windows program linked by MSVC,
// in `-Wl,` form so msvc_cl_args passes them after `/link`.
fn v3_msvc_link_flags(target_os string, is_shared bool, is_o bool, subsystem pref.Subsystem, windows_gui_app bool) []string {
if target_os != 'windows' || is_shared || is_o {
return []
}
// Match the 32 MiB main thread stack that gcc-style Windows builds reserve.
mut flags := ['-Wl,/STACK:33554432']
match subsystem {
.console {
flags << '-Wl,/SUBSYSTEM:CONSOLE'
}
.windows {
flags << '-Wl,/SUBSYSTEM:WINDOWS'
}
.auto {
if windows_gui_app {
flags << '-Wl,/SUBSYSTEM:WINDOWS'
}
}
}
return flags
}

// msvc_cl_args translates gcc-style C compiler arguments into arguments for MSVC's
// `cl`. Options without an MSVC counterpart (warning selection, code generation
// tuning like `-fwrapv`, or `-std=gnu11`) are dropped.
fn msvc_cl_args(args []string, target_os string) []string {
// `/bigobj`: the program is one translation unit with tens of thousands of functions.
mut compile := ['/nologo', '/volatile:ms', '/we4013', '/utf-8', '/bigobj', '/MD']
mut inputs := []string{}
mut libs := []string{}
mut link := []string{}
mut output := ''
mut compile_only := false
mut is_shared := false
mut is_debug := false
mut has_cpp := false
mut i := 0
for i < args.len {
arg := args[i]
i++
if arg.len == 0 {
continue
}
next := if i < args.len { args[i] } else { '' }
match arg {
'-o' {
output = next
i++
continue
}
'-c' {
compile_only = true
continue
}
'-shared' {
is_shared = true
continue
}
'-g', '-g3', '-ggdb' {
is_debug = true
continue
}
'-w' {
compile << '/w'
continue
}
'-x' {
i++
continue
Comment on lines +115 to +117

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the language selected by -x

When a native source uses -x c++ with a .c filename, this discards the language selection, leaves has_cpp false, and later adds /std:c11; MSVC then treats the file as C rather than C++. The driver deliberately tracks an active -x language for native inputs, so translate c++ to /TP (and suppress C11 mode) instead of dropping it. See MSVC's source-type options.

Useful? React with 👍 / 👎.

}
'-I', '-isystem', '-iquote', '-idirafter' {
compile << '/I${next}'
i++
continue
}
'-D' {
compile << '/D${next}'
i++
continue
}
'-U' {
compile << '/U${next}'
i++
continue
}
'-include' {
compile << '/FI${next}'
i++
continue
}
'-L' {
link << '/LIBPATH:${next}'
i++
continue
}
'-l' {
msvc_add_lib(mut libs, next)
i++
continue
}
'-Xlinker' {
msvc_add_linker_option(mut link, next)
i++
continue
}
'-mwindows' {
link << '/SUBSYSTEM:WINDOWS'
continue
}
'-mconsole' {
link << '/SUBSYSTEM:CONSOLE'
continue
}
else {}
}
if arg in msvc_ignored_options_with_operand {
i++
continue
}
if arg.starts_with('@') {
compile << arg
continue
}
lower := arg.to_lower_ascii()
if msvc_source_extensions.any(lower.ends_with(it)) && !arg.starts_with('-') {
if !lower.ends_with('.c') {
has_cpp = true
}
inputs << arg
continue
}
if msvc_linker_input_extensions.any(lower.ends_with(it)) && !arg.starts_with('-') {
if lower.ends_with('.def') {
link << '/DEF:${arg}'
} else {
inputs << arg
}
continue
}
if arg.starts_with('-I') {
compile << '/I${arg[2..]}'
} else if arg.starts_with('-D') {
compile << '/D${arg[2..]}'
} else if arg.starts_with('-U') {
compile << '/U${arg[2..]}'
} else if arg.starts_with('-L') {
link << '/LIBPATH:${arg[2..]}'
} else if arg.starts_with('-l') {
msvc_add_lib(mut libs, arg[2..])
} else if arg.starts_with('-Wl,') {
for part in arg[4..].split(',') {
msvc_add_linker_option(mut link, part)
}
} else if arg.starts_with('-O') {
level := arg[2..]
compile << match level {
'0' { '/Od' }
's', 'z', '1' { '/O1' }
else { '/O2' }
}
} else if arg == '-Werror=implicit-function-declaration' {
// Already enabled by the default `/we4013`.
} else if arg.starts_with('/') && !os.exists(arg) {
// An MSVC option given directly, for example with `-cflags`.
compile << arg
Comment on lines +211 to +213

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route native linker options after /link

When users pass a native linker switch such as /LIBPATH:C:\libs or /NODEFAULTLIB:foo through -cflags, this branch puts it in compile, before the source inputs and the generated /link; cl therefore interprets or rejects it as a compiler option instead of forwarding it to link.exe. Classify linker-only slash switches into link, and handle a direct /link marker without placing it before the input files. See MSVC's /link option.

Useful? React with 👍 / 👎.

} else if !arg.starts_with('-') {
// Another linker input, such as an extensionless library path.
inputs << arg
}
// Any other gcc-style option (`-W...`, `-f...`, `-m...`, `-std=...`, `-pthread`,
// `-municode`, `-M...`) has no MSVC equivalent that V relies on.
}
if !has_cpp {
// C11 mode also enables MSVC's conforming preprocessor.
compile << '/std:c11'
}
if is_debug {
compile << '/Zi'
link << '/DEBUG'
}
if compile_only {
compile << '/c'
if output.len > 0 {
compile << '/Fo${output}'
}
} else {
if is_shared {
compile << '/LD'
}
if output.len > 0 {
compile << '/Fe${output}'
}
}
mut res := compile.clone()
res << inputs
if !compile_only {
if target_os == 'windows' {
for lib in msvc_default_libs {
if lib !in libs {
res << lib
}
}
}
res << libs
if link.len > 0 {
res << '/link'
res << link
}
}
return res
}

fn msvc_add_lib(mut libs []string, name string) {
mut lib := name.trim_space()
if lib.starts_with(':') {
lib = lib[1..]
}
if lib.len == 0 || lib in msvc_ignored_libs {
return
}
if !lib.to_lower_ascii().ends_with('.lib') {
lib += '.lib'
}
if lib !in libs {
libs << lib
}
}

// msvc_add_linker_option translates one gcc-style linker option (the operand of
// `-Wl,` or `-Xlinker`) for MSVC's `link`.
fn msvc_add_linker_option(mut link []string, option string) {
if option.len == 0 {
return
}
if option.starts_with('/') {
link << option
return
}
if option.starts_with('--stack=') {
link << '/STACK:${option.all_after('=')}'
} else if option.starts_with('--subsystem=') || option.starts_with('-subsystem=') {
link << '/SUBSYSTEM:${option.all_after('=').to_upper_ascii()}'
} else if option.starts_with('-L') {
link << '/LIBPATH:${option[2..]}'
}
// Other GNU linker options (`-rpath`, `--as-needed`, ...) have no MSVC equivalent.
}

// msvc_lower_c_file rewrites a generated C file in place, so MSVC can compile it.
fn msvc_lower_c_file(path string) ! {
source := os.read_file(path)!
os.write_file(path, cgen.msvc_compat_c_source(source))!
}

// msvc_require_cl exits with an explanation when MSVC's compiler cannot be run.
fn msvc_require_cl(c_compiler string, host_os string) {
os.find_abs_path_of_executable(c_compiler) or {
if host_os == 'windows' {
eprintln('`-cc msvc` could not find `${c_compiler}`. Run V from a Visual Studio Developer Command Prompt (or after `vcvars64.bat`), so that `cl` and its INCLUDE/LIB environment are available.')
} else {
eprintln('`-cc msvc` can only compile on Windows; use `-o file.c` to generate C for MSVC on ${host_os}.')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the Windows target in the cross-host fallback

On a non-Windows host when the original command did not specify -os windows, following this diagnostic with -cc msvc -o file.c still generates C for the host target, including its platform-specific V sources and declarations, so the result cannot be compiled by Windows MSVC. The suggested command needs to include -os windows (or otherwise force the target) to produce the promised MSVC-compatible Windows source.

Useful? React with 👍 / 👎.

}
exit(1)
}
}
Loading
Loading