From 00801e3f768516ac9e71d7b8585b1e91b848d230 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 10 Jul 2026 01:53:14 +0300 Subject: [PATCH 1/2] vlib: i18n module --- vlib/i18n/i18n.v | 104 +++++++++++++++++++++++ vlib/i18n/i18n_test.v | 30 +++++++ vlib/i18n/testdata/translations/en.tr | 2 + vlib/i18n/testdata/translations/pt-br.tr | 2 + vlib/veb/tr.v | 67 ++------------- 5 files changed, 143 insertions(+), 62 deletions(-) create mode 100644 vlib/i18n/i18n.v create mode 100644 vlib/i18n/i18n_test.v create mode 100644 vlib/i18n/testdata/translations/en.tr create mode 100644 vlib/i18n/testdata/translations/pt-br.tr diff --git a/vlib/i18n/i18n.v b/vlib/i18n/i18n.v new file mode 100644 index 00000000000000..bdeef831548a2d --- /dev/null +++ b/vlib/i18n/i18n.v @@ -0,0 +1,104 @@ +// Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module i18n + +import os + +pub const default_translations_dir = 'translations' + +const default_tr_map = load_tr_map() + +// load_tr_map loads all .tr files from the default translations directory. +pub fn load_tr_map() map[string]map[string]string { + return load_tr_map_from_dir(default_translations_dir) +} + +// load_tr_map_from_dir loads all .tr files from dir into a lang -> key -> text map. +pub fn load_tr_map_from_dir(dir string) map[string]map[string]string { + files := os.walk_ext(dir, '.tr') + mut res := map[string]map[string]string{} + for tr_path in files { + lang := fetch_lang_from_tr_path(tr_path) + if lang.len == 0 { + continue + } + text := os.read_file(tr_path) or { + eprintln('translation file "${tr_path}" failed to load') + return {} + } + for key, val in parse_tr_text(text) { + res[lang][key] = val + } + } + return res +} + +fn parse_tr_text(text string) map[string]string { + mut res := map[string]string{} + normalized := text.replace('\r\n', '\n') + for section in normalized.split('-----\n') { + nl_pos := section.index('\n') or { continue } + key := section[..nl_pos].trim_space() + if key.len == 0 { + continue + } + res[key] = section[nl_pos + 1..].trim_right('\n') + } + return res +} + +fn fetch_lang_from_tr_path(path string) string { + return os.file_name(path).all_before_last('.tr') +} + +// tr returns the translation for key from the default translations directory. +pub fn tr(lang string, key string) string { + return tr_from_map(default_tr_map, lang, key) +} + +// tr_from_map returns the translation for key from translations. +pub fn tr_from_map(translations map[string]map[string]string, lang string, key string) string { + res := translations[lang][key] + if res == '' { + eprintln('NO TRANSLATION FOR KEY "${key}"') + return key + } + return res +} + +// tr_plural returns the pluralized translation for key from the default translations directory. +pub fn tr_plural(lang string, key string, amount int) string { + return tr_plural_from_map(default_tr_map, lang, key, amount) +} + +// tr_plural_from_map returns the pluralized translation for key from translations. +pub fn tr_plural_from_map(translations map[string]map[string]string, lang string, key string, amount int) string { + s := translations[lang][key] + if s == '' { + eprintln('NO TRANSLATION FOR KEY "${key}"') + return key + } + if !s.contains('|') { + return s + } + // goods + // товар|а|ов + vals := s.split('|') + if vals.len != 3 { + return s + } + amount_str := amount.str() + ending := if amount % 10 == 1 && !amount_str.ends_with('11') { + '' + } else if amount % 10 == 2 && !amount_str.ends_with('12') { + vals[1] + } else if amount % 10 == 3 && !amount_str.ends_with('13') { + vals[1] + } else if amount % 10 == 4 && !amount_str.ends_with('14') { + vals[1] + } else { + vals[2] + } + return vals[0] + ending +} diff --git a/vlib/i18n/i18n_test.v b/vlib/i18n/i18n_test.v new file mode 100644 index 00000000000000..2b0fc32520dd1f --- /dev/null +++ b/vlib/i18n/i18n_test.v @@ -0,0 +1,30 @@ +module i18n + +import os + +fn test_load_tr_map_from_dir() { + translations := load_tr_map_from_dir(os.join_path(os.dir(@FILE), 'testdata', 'translations')) + + assert 'en' in translations + assert 'pt-br' in translations + assert translations['en']['msg_hello'] == 'Hello' + assert translations['pt-br']['msg_hello'] == 'Ola' +} + +fn test_tr_from_map_returns_key_for_missing_translation() { + translations := load_tr_map_from_dir(os.join_path(os.dir(@FILE), 'testdata', 'translations')) + + assert tr_from_map(translations, 'en', 'missing_key') == 'missing_key' +} + +fn test_tr_plural_from_map() { + translations := { + 'ru': { + 'goods': 'товар|а|ов' + } + } + + assert tr_plural_from_map(translations, 'ru', 'goods', 1) == 'товар' + assert tr_plural_from_map(translations, 'ru', 'goods', 2) == 'товара' + assert tr_plural_from_map(translations, 'ru', 'goods', 5) == 'товаров' +} diff --git a/vlib/i18n/testdata/translations/en.tr b/vlib/i18n/testdata/translations/en.tr new file mode 100644 index 00000000000000..ab11b2f05059b6 --- /dev/null +++ b/vlib/i18n/testdata/translations/en.tr @@ -0,0 +1,2 @@ +msg_hello +Hello diff --git a/vlib/i18n/testdata/translations/pt-br.tr b/vlib/i18n/testdata/translations/pt-br.tr new file mode 100644 index 00000000000000..5698f9afeab300 --- /dev/null +++ b/vlib/i18n/testdata/translations/pt-br.tr @@ -0,0 +1,2 @@ +msg_hello +Ola diff --git a/vlib/veb/tr.v b/vlib/veb/tr.v index a5e13a715748cd..fb295658c3917f 100644 --- a/vlib/veb/tr.v +++ b/vlib/veb/tr.v @@ -3,9 +3,9 @@ // that can be found in the LICENSE file. module veb -import os +import i18n -const tr_map = load_tr_map() +const tr_map = i18n.load_tr_map() pub fn raw(s string) RawHtml { return RawHtml(s) @@ -14,71 +14,14 @@ pub fn raw(s string) RawHtml { // This function is run once, on app startup. Setting the `tr_map` const. // m['en']['house'] == 'House' fn load_tr_map() map[string]map[string]string { - // Find all translation files to figure out how many languages we have and to load the translation map - files := os.walk_ext('translations', '.tr') - mut res := map[string]map[string]string{} - for tr_path in files { - lang := fetch_lang_from_tr_path(tr_path) - text := os.read_file(tr_path) or { - eprintln('translation file "${tr_path}" failed to laod') - return {} - } - x := text.split('-----\n') - for s in x { - nl_pos := s.index('\n') or { continue } - key := s[..nl_pos] - val := s[nl_pos + 1..] - res[lang][key] = val - } - } - return res -} - -fn fetch_lang_from_tr_path(path string) string { - return path.find_between(os.path_separator, '.') + return i18n.load_tr_map() } // Used by %key in templates pub fn tr(lang string, key string) string { - res := tr_map[lang][key] - if res == '' { - eprintln('NO TRANSLATION FOR KEY "${key}"') - return key - } - return RawHtml(res) + return RawHtml(i18n.tr_from_map(tr_map, lang, key)) } pub fn tr_plural(lang string, key string, amount int) string { - s := tr_map[lang][key] - if s == '' { - eprintln('NO TRANSLATION FOR KEY "${key}"') - return key - } - if s.contains('|') { - //----- - // goods - // товар|а|ов - vals := s.split('|') - if vals.len != 3 { - return s - } - amount_str := amount.str() - // 1, 21, 121 товар - ending := if amount % 10 == 1 && !amount_str.ends_with('11') { // vals[0] - '' - // 2, 3, 4, 22 товара - } else if amount % 10 == 2 && !amount_str.ends_with('12') { - vals[1] - } else if amount % 10 == 3 && !amount_str.ends_with('13') { - vals[1] - } else if amount % 10 == 4 && !amount_str.ends_with('14') { - vals[1] - } else { - // 5 товаров, 11 товаров etc - vals[2] - } - return vals[0] + ending - } else { - return s - } + return i18n.tr_plural_from_map(tr_map, lang, key, amount) } From ebbd93303224bcae52be0f34a479d6f76cba7c87 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 10 Jul 2026 02:21:33 +0300 Subject: [PATCH 2/2] builder,v3: fix shared library builds --- vlib/v/builder/cc.v | 2 +- vlib/v/builder/cc_test.v | 8 + vlib/v3/gen/c/cleanc.v | 2 +- vlib/v3/gen/c/fn.v | 8 +- .../gen/c/fn_parallel_notd_v3_no_parallel.v | 10 +- vlib/v3/gen/c/types.v | 26 +-- vlib/v3/parser/parser.v | 6 +- vlib/v3/tests/shared_flag_test.v | 153 ++++++++++++++++ vlib/v3/transform/if.v | 8 +- vlib/v3/transform/monomorphize.v | 21 +-- vlib/v3/transform/or.v | 19 +- vlib/v3/transform/transform.v | 3 +- vlib/v3/types/checker.v | 168 +++++++++++------- vlib/v3/v3.v | 87 +++++++-- 14 files changed, 398 insertions(+), 123 deletions(-) create mode 100644 vlib/v3/tests/shared_flag_test.v diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 1313038f927621..83a0c6c87935fb 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -1213,7 +1213,7 @@ fn (v &Builder) only_linker_args(ccoptions CcompilerOptions) []string { mut all := []string{} // in `build-mode` or when producing a .o file, we do not need -lxyz flags, // since we are building an (.o) object file, that will be linked later. - if v.pref.build_mode != .build_module && !v.pref.is_o { + if (v.pref.build_mode != .build_module || v.pref.is_shared) && !v.pref.is_o { all << ccoptions.linker_flags all << ccoptions.env_ldflags all << ccoptions.ldflags diff --git a/vlib/v/builder/cc_test.v b/vlib/v/builder/cc_test.v index 02647f96599efc..0047c0a302431e 100644 --- a/vlib/v/builder/cc_test.v +++ b/vlib/v/builder/cc_test.v @@ -540,6 +540,14 @@ fn test_shared_tcc_compile_args_skip_bt25_after_late_compiler_resolution() { assert !builder.get_compile_args().contains('-bt25') } +fn test_shared_build_module_keeps_shared_linker_flag() { + mut builder := new_test_builder(['-shared', hello_world_example()]) + builder.pref.build_mode = .build_module + linker_args := builder.get_linker_args() + + assert linker_args.contains('-shared') +} + fn test_linux_shared_boehm_linker_args_hide_static_gc_archive_symbols() { linker_args := builder_linker_args_with_cc([ '-os', diff --git a/vlib/v3/gen/c/cleanc.v b/vlib/v3/gen/c/cleanc.v index fce809e47be022..b5288c216c4248 100644 --- a/vlib/v3/gen/c/cleanc.v +++ b/vlib/v3/gen/c/cleanc.v @@ -8164,7 +8164,7 @@ fn (mut g FlatGen) builtin_abi_decls() { g.writeln('static inline string v3_int_zpad(int n, int width) { string s = int__str(n); if (n < 0) return s; while (s.len < width) s = string__plus(v3_c_lit("0", 1), s); return s; }') g.writeln('static inline string v3_i64_zpad(i64 n, int width) { string s = i64__str(n); if (n < 0) return s; while (s.len < width) s = string__plus(v3_c_lit("0", 1), s); return s; }') g.writeln('static inline string v3_u64_zpad(u64 n, int width) { string s = u64__str(n); while (s.len < width) s = string__plus(v3_c_lit("0", 1), s); return s; }') - g.writeln('static inline string v3_string_zpad(string s, int width) { if (s.len >= width) return s; int sign = s.len > 0 && s.str[0] == \'-\'; int pad = width - s.len; u8* out = malloc_noscan((ptrdiff_t)width + 1); int pos = 0; if (sign) out[pos++] = \'-\'; memset(out + pos, \'0\', (size_t)pad); pos += pad; memcpy(out + pos, s.str + sign, (size_t)(s.len - sign)); out[width] = 0; return (string){.str = out, .len = width, .is_lit = 0}; }') + g.writeln("static inline string v3_string_zpad(string s, int width) { if (s.len >= width) return s; int sign = s.len > 0 && s.str[0] == '-'; int pad = width - s.len; u8* out = malloc_noscan((ptrdiff_t)width + 1); int pos = 0; if (sign) out[pos++] = '-'; memset(out + pos, '0', (size_t)pad); pos += pad; memcpy(out + pos, s.str + sign, (size_t)(s.len - sign)); out[width] = 0; return (string){.str = out, .len = width, .is_lit = 0}; }") g.writeln('static inline i64 v3_map_signed(void* p, int bytes) { if (bytes == 1) return *(signed char*)p; if (bytes == 2) return *(short*)p; if (bytes == 8) return *(long long*)p; return *(int*)p; }') g.writeln('static inline u64 v3_map_unsigned(void* p, int bytes) { if (bytes == 1) return *(unsigned char*)p; if (bytes == 2) return *(unsigned short*)p; if (bytes == 8) return *(unsigned long long*)p; return *(unsigned int*)p; }') g.writeln('static inline string v3_f32_array_str(float* vals, int n) { string out = v3_c_lit("[", 1); for (int i = 0; i < n; ++i) { if (i > 0) out = string__plus(out, v3_c_lit(", ", 2)); out = string__plus(out, f64__str((double)vals[i])); } return string__plus(out, v3_c_lit("]", 1)); }') diff --git a/vlib/v3/gen/c/fn.v b/vlib/v3/gen/c/fn.v index 4fad04da48c8f5..e2239bf32b9df1 100644 --- a/vlib/v3/gen/c/fn.v +++ b/vlib/v3/gen/c/fn.v @@ -4014,8 +4014,8 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) { receiver_wants_shared := g.fn_param_is_shared(actual_fn, 0) || g.fn_param_is_shared(method_name, 0) || g.fn_param_is_shared(fn_name, 0) || g.fn_param_is_shared(emitted_callee_name, 0) - if receiver_wants_shared - && (g.gen_shared_local_receiver_arg(base_id) || g.gen_shared_storage_expr(base_id)) { + if receiver_wants_shared && (g.gen_shared_local_receiver_arg(base_id) + || g.gen_shared_storage_expr(base_id)) { arg_start = 1 } else if param_types.len > 0 && g.gen_embedded_method_receiver(base_id, base_type, param_types[0], receiver_wants_ptr) { @@ -4132,8 +4132,8 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) { || g.fn_param_is_shared(g.direct_call_name(target_name), arg_idx) || g.fn_param_is_shared(g.direct_call_name(fn_name), arg_idx) || g.fn_param_is_shared(g.direct_call_name(emitted_callee_name), arg_idx) - if arg_param_is_shared - && (g.gen_shared_local_receiver_arg(arg_id) || g.gen_shared_storage_expr(arg_id)) { + if arg_param_is_shared && (g.gen_shared_local_receiver_arg(arg_id) + || g.gen_shared_storage_expr(arg_id)) { continue } } diff --git a/vlib/v3/gen/c/fn_parallel_notd_v3_no_parallel.v b/vlib/v3/gen/c/fn_parallel_notd_v3_no_parallel.v index 1e78631c213445..bbbd43ac2a8a77 100644 --- a/vlib/v3/gen/c/fn_parallel_notd_v3_no_parallel.v +++ b/vlib/v3/gen/c/fn_parallel_notd_v3_no_parallel.v @@ -449,11 +449,11 @@ fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen { const_modules: g.const_modules const_init_order: g.const_init_order fixed_storage_consts: g.fixed_storage_consts - global_modules: g.global_modules - global_inits: g.global_inits - global_init_order: g.global_init_order - enum_backing_infos: g.enum_backing_infos - iface_impls: g.iface_impls + global_modules: g.global_modules + global_inits: g.global_inits + global_init_order: g.global_init_order + enum_backing_infos: g.enum_backing_infos + iface_impls: g.iface_impls iface_type_ids: g.iface_type_ids ierror_method_emit_names: g.ierror_method_emit_names ierror_stack_pointer_aliases: []map[string]bool{} diff --git a/vlib/v3/gen/c/types.v b/vlib/v3/gen/c/types.v index 8cbb4c320e4604..7d0d89c59b47d5 100644 --- a/vlib/v3/gen/c/types.v +++ b/vlib/v3/gen/c/types.v @@ -471,7 +471,11 @@ fn (mut g FlatGen) enum_decls() { f := g.a.child_node(&node, i) mut value := next_value mut value_known := next_value_known - mut value_expr := if next_value_known { value.str() } else { next_value_expr } + mut value_expr := if next_value_known { + value.str() + } else { + next_value_expr + } if f.children_count > 0 { expr_id := g.a.child(f, 0) mut resolving := map[string]bool{} @@ -799,15 +803,15 @@ fn (g &FlatGen) enum_field_expr_value_with_enum(id flat.NodeId, enum_module stri if node.children_count == 0 { return none } - return g.enum_field_expr_value_with_enum(g.a.child(&node, 0), enum_module, enum_name, - mut field_values, field_exprs, mut resolving) + return g.enum_field_expr_value_with_enum(g.a.child(&node, 0), enum_module, enum_name, mut + field_values, field_exprs, mut resolving) } .prefix { if node.children_count == 0 { return none } - value := g.enum_field_expr_value_with_enum(g.a.child(&node, 0), enum_module, enum_name, - mut field_values, field_exprs, mut resolving)? + value := g.enum_field_expr_value_with_enum(g.a.child(&node, 0), enum_module, enum_name, mut + field_values, field_exprs, mut resolving)? return match node.op { .plus { value } .minus { -value } @@ -819,10 +823,10 @@ fn (g &FlatGen) enum_field_expr_value_with_enum(id flat.NodeId, enum_module stri if node.children_count < 2 { return none } - left := g.enum_field_expr_value_with_enum(g.a.child(&node, 0), enum_module, enum_name, - mut field_values, field_exprs, mut resolving)? - right := g.enum_field_expr_value_with_enum(g.a.child(&node, 1), enum_module, enum_name, - mut field_values, field_exprs, mut resolving)? + left := g.enum_field_expr_value_with_enum(g.a.child(&node, 0), enum_module, enum_name, mut + field_values, field_exprs, mut resolving)? + right := g.enum_field_expr_value_with_enum(g.a.child(&node, 1), enum_module, enum_name, mut + field_values, field_exprs, mut resolving)? if (node.op == .div || node.op == .mod) && right == 0 { return none } @@ -847,8 +851,8 @@ fn (g &FlatGen) enum_field_expr_value_with_enum(id flat.NodeId, enum_module stri } .selector { if field := g.enum_decl_selector_ref_field(id, enum_module, enum_name) { - return g.enum_decl_field_ref_value(field, enum_module, enum_name, mut - field_values, field_exprs, mut resolving) + return g.enum_decl_field_ref_value(field, enum_module, enum_name, mut field_values, + field_exprs, mut resolving) } return none } diff --git a/vlib/v3/parser/parser.v b/vlib/v3/parser/parser.v index f90d62385313d7..21d061f4421e8f 100644 --- a/vlib/v3/parser/parser.v +++ b/vlib/v3/parser/parser.v @@ -960,7 +960,11 @@ fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type prev_struct := p.cur_struct p.cur_fn = name // `@STRUCT` inside a method expands to the receiver's (dereferenced) type name. - p.cur_struct = if is_method { method_receiver_type_name(receiver_type).all_after_last('.') } else { '' } + p.cur_struct = if is_method { + method_receiver_type_name(receiver_type).all_after_last('.') + } else { + '' + } p.push_local_type_scope(name) // A disabled `@[if flag ?]` function keeps its signature but gets an empty body // (a no-op stub), so callers still resolve while the body is compiled out. diff --git a/vlib/v3/tests/shared_flag_test.v b/vlib/v3/tests/shared_flag_test.v new file mode 100644 index 00000000000000..41330b531c6819 --- /dev/null +++ b/vlib/v3/tests/shared_flag_test.v @@ -0,0 +1,153 @@ +import os + +const shared_flag_vexe = @VEXE +const shared_flag_tests_dir = os.dir(@FILE) +const shared_flag_v3_dir = os.dir(shared_flag_tests_dir) +const shared_flag_v3_src = os.join_path(shared_flag_v3_dir, 'v3.v') + +// test_shared_flag_builds_no_main_module validates this v3 regression case. +fn test_shared_flag_builds_no_main_module() { + v3_bin := os.join_path(os.temp_dir(), 'v3_shared_flag_test_${os.getpid()}') + build := + os.execute('${os.quoted_path(shared_flag_vexe)} -o ${os.quoted_path(v3_bin)} ${os.quoted_path(shared_flag_v3_src)}') + assert build.exit_code == 0, build.output + + tmp_dir := os.join_path(os.temp_dir(), 'v3_shared_flag_module_${os.getpid()}') + os.rmdir_all(tmp_dir) or {} + os.mkdir_all(tmp_dir)! + defer { + os.rmdir_all(tmp_dir) or {} + os.rm(v3_bin) or {} + } + + os.write_file(os.join_path(tmp_dir, 'v.mod'), 'Module { name: "shared_flag_module" }\n')! + os.write_file(os.join_path(tmp_dir, 'module.v'), + 'module shared_flag_module\n\npub fn answer() int {\n\treturn 42\n}\n')! + + out_lib := os.join_path(os.temp_dir(), 'v3_shared_flag_module_${os.getpid()}') + out_path := out_lib + shared_flag_library_postfix() + os.rm(out_path) or {} + defer { + os.rm(out_path) or {} + } + + compile := + os.execute('${os.quoted_path(v3_bin)} -shared -o ${os.quoted_path(out_lib)} ${os.quoted_path(tmp_dir)}') + assert compile.exit_code == 0, compile.output + assert compile.output.contains('-shared'), compile.output + assert !compile.output.contains('_main not defined'), compile.output + assert os.exists(out_path) + assert os.file_size(out_path) > 0 +} + +// test_shared_flag_builds_object_dependencies_as_pic validates that cached +// #flag .o dependencies are rebuilt with -fPIC for shared-library builds. +fn test_shared_flag_builds_object_dependencies_as_pic() { + $if windows { + return + } + pid := os.getpid() + v3_bin := os.join_path(os.temp_dir(), 'v3_shared_flag_pic_test_${pid}') + build := + os.execute('${os.quoted_path(shared_flag_vexe)} -o ${os.quoted_path(v3_bin)} ${os.quoted_path(shared_flag_v3_src)}') + assert build.exit_code == 0, build.output + + tmp_dir := os.join_path(os.temp_dir(), 'v3_shared_flag_pic_${pid}') + os.rmdir_all(tmp_dir) or {} + os.mkdir_all(tmp_dir)! + defer { + os.rmdir_all(tmp_dir) or {} + os.rm(v3_bin) or {} + } + + native_dir := os.join_path(tmp_dir, 'native') + os.mkdir_all(native_dir)! + obj_path := os.join_path(native_dir, 'pic_dep.o') + os.write_file(os.join_path(native_dir, 'pic_dep.c'), 'int v3_pic_dep_global = 41; +int v3_pic_dep_value(void) { + return v3_pic_dep_global + 1; +} +')! + shared_flag_remove_cached_objects(obj_path) + defer { + shared_flag_remove_cached_objects(obj_path) + } + + app_dir := os.join_path(tmp_dir, 'app') + os.mkdir_all(app_dir)! + app_src := os.join_path(app_dir, 'main.v') + os.write_file(app_src, '#flag ${obj_path} +fn C.v3_pic_dep_value() int + +fn main() { + println(C.v3_pic_dep_value().str()) +} +')! + app_bin := os.join_path(tmp_dir, 'app_bin') + compile_app := + os.execute('${os.quoted_path(v3_bin)} -o ${os.quoted_path(app_bin)} ${os.quoted_path(app_src)}') + assert compile_app.exit_code == 0, compile_app.output + cached_after_app := shared_flag_cached_objects(obj_path) + assert cached_after_app.len == 1, cached_after_app.str() + run_app := os.execute(os.quoted_path(app_bin)) + assert run_app.exit_code == 0, run_app.output + assert run_app.output.trim_space() == '42' + + lib_dir := os.join_path(tmp_dir, 'lib') + os.mkdir_all(lib_dir)! + os.write_file(os.join_path(lib_dir, 'v.mod'), 'Module { name: "shared_pic_dep" }\n')! + os.write_file(os.join_path(lib_dir, 'module.v'), 'module shared_pic_dep + +#flag ${obj_path} +fn C.v3_pic_dep_value() int + +pub fn answer() int { + return C.v3_pic_dep_value() +} +')! + out_lib := os.join_path(tmp_dir, 'shared_pic_dep') + out_path := out_lib + shared_flag_library_postfix() + compile_shared := + os.execute('${os.quoted_path(v3_bin)} -shared -o ${os.quoted_path(out_lib)} ${os.quoted_path(lib_dir)}') + assert compile_shared.exit_code == 0, compile_shared.output + assert compile_shared.output.contains('-fPIC'), compile_shared.output + cached_after_shared := shared_flag_cached_objects(obj_path) + assert cached_after_shared.len == 2, '${compile_shared.output}\n${cached_after_shared}' + assert os.exists(out_path) + assert os.file_size(out_path) > 0 +} + +fn shared_flag_library_postfix() string { + $if windows { + return '.dll' + } $else $if macos { + return '.dylib' + } $else { + return '.so' + } +} + +fn shared_flag_cached_objects(obj_path string) []string { + cache_dir := os.join_path(os.vtmp_dir(), 'v3_thirdparty_objs') + if !os.exists(cache_dir) { + return [] + } + prefix := shared_flag_object_cache_prefix(obj_path) + mut found := []string{} + for path in os.walk_ext(cache_dir, '.o') { + if os.file_name(path).starts_with(prefix) { + found << path + } + } + return found +} + +fn shared_flag_remove_cached_objects(obj_path string) { + for path in shared_flag_cached_objects(obj_path) { + os.rm(path) or {} + } +} + +fn shared_flag_object_cache_prefix(path string) string { + return path.replace_each(['/', '_', '\\', '_', ':', '_', '.', '_', ' ', '_']) + '_' +} diff --git a/vlib/v3/transform/if.v b/vlib/v3/transform/if.v index 1af8c468bf0e22..c796314dc84c04 100644 --- a/vlib/v3/transform/if.v +++ b/vlib/v3/transform/if.v @@ -248,8 +248,8 @@ fn (mut t Transformer) expand_array_index_if_guard(node flat.Node, lhs_name stri idx_ident := t.make_ident(index_name) lower_ok := t.make_infix(.ge, idx_ident, t.make_int_literal(0)) - upper_ok := t.make_infix(.lt, t.make_ident(index_name), t.array_index_len_expr(info, - array_expr)) + upper_ok := t.make_infix(.lt, t.make_ident(index_name), + t.array_index_len_expr(info, array_expr)) found_cond := t.make_infix(.logical_and, lower_ok, upper_ok) then_id := t.a.child(&node, 1) @@ -915,8 +915,8 @@ fn (mut t Transformer) build_array_index_if_value_guard_chain(if_node flat.Node, result << t.make_decl_assign_typed(index_name, index_expr, 'int') idx_ident := t.make_ident(index_name) lower_ok := t.make_infix(.ge, idx_ident, t.make_int_literal(0)) - upper_ok := t.make_infix(.lt, t.make_ident(index_name), t.array_index_len_expr(info, - array_expr)) + upper_ok := t.make_infix(.lt, t.make_ident(index_name), + t.array_index_len_expr(info, array_expr)) found_cond := t.make_infix(.logical_and, lower_ok, upper_ok) saved_var_types := t.var_types.clone() diff --git a/vlib/v3/transform/monomorphize.v b/vlib/v3/transform/monomorphize.v index 4f3a2785815298..812fae26b824f5 100644 --- a/vlib/v3/transform/monomorphize.v +++ b/vlib/v3/transform/monomorphize.v @@ -4332,12 +4332,12 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is for i in 0 .. node.children_count { children << t.clone_generic_node(t.a.child(&node, i), args) } - if is_comptime_for { - t.cloning_comptime_for_depth-- - } - mut cloned_typ := t.resolve_substituted_type_text(t.subst_type(node.typ, args)) - t.substitute_cloned_generic_call_type_args(node, mut children, args) - if t.cloning_comptime_for_depth > 0 { + if is_comptime_for { + t.cloning_comptime_for_depth-- + } + mut cloned_typ := t.resolve_substituted_type_text(t.subst_type(node.typ, args)) + t.substitute_cloned_generic_call_type_args(node, mut children, args) + if t.cloning_comptime_for_depth > 0 { // Inside a `$for` body: clone verbatim, no generic-call retargeting. start2 := t.a.children.len for child in children { @@ -4404,7 +4404,7 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is t.retarget_cloned_implicit_generic_call(clone_id, node, args) } return clone_id - } +} fn (mut t Transformer) substitute_cloned_generic_call_type_args(node flat.Node, mut children []flat.NodeId, args []string) { if node.kind != .index || node.children_count < 2 || node.value == 'range' @@ -4417,7 +4417,8 @@ fn (mut t Transformer) substitute_cloned_generic_call_type_args(node flat.Node, continue } substituted := t.resolve_substituted_type_text(t.subst_type(arg, args)) - if substituted.len == 0 || substituted == arg || t.generic_args_have_placeholders([substituted]) { + if substituted.len == 0 || substituted == arg + || t.generic_args_have_placeholders([substituted]) { continue } children[i] = t.make_ident(substituted) @@ -4695,8 +4696,8 @@ fn (mut t Transformer) retarget_cloned_implicit_generic_call(clone_id flat.NodeI } source_arg := t.a.nodes[int(source_arg_id)] mut arg_type := t.generic_call_arg_type_for_inference(clone_arg_id) - source_arg_type := - t.resolve_substituted_type_text(t.subst_type(source_arg.typ, active_args)) + source_arg_type := t.resolve_substituted_type_text(t.subst_type(source_arg.typ, + active_args)) if decl_type_is_usable(source_arg_type) && !t.generic_arg_is_unresolved(source_arg_type) { arg_type = source_arg_type } diff --git a/vlib/v3/transform/or.v b/vlib/v3/transform/or.v index d75dc4c49f6ffe..b2947b90ccf80a 100644 --- a/vlib/v3/transform/or.v +++ b/vlib/v3/transform/or.v @@ -187,8 +187,8 @@ fn (mut t Transformer) channel_receive_info(expr_id flat.NodeId) ?ChannelReceive value_type := t.normalize_type_alias(clean_type.elem_type.name()) if value_type.len > 0 { return ChannelReceiveInfo{ - channel_id: channel_id - value_type: value_type + channel_id: channel_id + value_type: value_type needs_deref: raw_type is types.Pointer } } @@ -197,8 +197,8 @@ fn (mut t Transformer) channel_receive_info(expr_id flat.NodeId) ?ChannelReceive value_type := t.normalize_type_alias(recv_type.name()) if value_type.len > 0 && value_type !in ['void', 'unknown'] { return ChannelReceiveInfo{ - channel_id: channel_id - value_type: value_type + channel_id: channel_id + value_type: value_type needs_deref: source_needs_deref } } @@ -208,8 +208,8 @@ fn (mut t Transformer) channel_receive_info(expr_id flat.NodeId) ?ChannelReceive value_type := t.normalize_type_alias(expr.typ) if value_type.len > 0 && value_type !in ['void', 'unknown'] { return ChannelReceiveInfo{ - channel_id: channel_id - value_type: value_type + channel_id: channel_id + value_type: value_type needs_deref: source_needs_deref } } @@ -231,8 +231,8 @@ fn (mut t Transformer) channel_receive_info(expr_id flat.NodeId) ?ChannelReceive return none } return ChannelReceiveInfo{ - channel_id: channel_id - value_type: value_type + channel_id: channel_id + value_type: value_type needs_deref: fallback_needs_deref } } @@ -390,8 +390,7 @@ fn (mut t Transformer) transform_array_index_or_expr(id flat.NodeId, node flat.N idx_ident := t.make_ident(index_name) lower_ok := t.make_infix(.ge, idx_ident, t.make_int_literal(0)) - upper_ok := t.make_infix(.lt, t.make_ident(index_name), t.array_index_len_expr(info, - base_expr)) + upper_ok := t.make_infix(.lt, t.make_ident(index_name), t.array_index_len_expr(info, base_expr)) found_cond := t.make_infix(.logical_and, lower_ok, upper_ok) else_block := t.make_block(t.lower_or_body_to_stmts_with_err_expr(body_id, val_name, result_type, node.value, t.make_ierror_none())) diff --git a/vlib/v3/transform/transform.v b/vlib/v3/transform/transform.v index 8ba06ed7b91be4..7e9b4a80d25f49 100644 --- a/vlib/v3/transform/transform.v +++ b/vlib/v3/transform/transform.v @@ -866,7 +866,8 @@ fn (mut t Transformer) collect_types() { } } t.enum_types[node.value] = field_names - backing_storage_type := if node.generic_params.len > 0 && node.generic_params[0].len > 0 { + backing_storage_type := if node.generic_params.len > 0 + && node.generic_params[0].len > 0 { t.normalize_type_in_module(node.generic_params[0], cur_mod) } else { '' diff --git a/vlib/v3/types/checker.v b/vlib/v3/types/checker.v index 3c8c9f30b0b258..b20a340c290be5 100644 --- a/vlib/v3/types/checker.v +++ b/vlib/v3/types/checker.v @@ -1105,8 +1105,7 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) { ptypes << tc.parse_type(child.typ) } } - tc.register_fn_signature(node.value, ret_type, ptypes, []bool{}, is_variadic, - false) + tc.register_fn_signature(node.value, ret_type, ptypes, []bool{}, is_variadic, false) if is_variadic { tc.register_c_variadic_fn(node.value) } @@ -2329,10 +2328,10 @@ fn (mut tc TypeChecker) annotate_assign_expected_exprs(node flat.Node) { mut i := 0 for i + 1 < node.children_count { lhs_id := tc.a.child(&node, i) - rhs_id := tc.a.child(&node, i + 1) - lhs_type := tc.resolve_lvalue_type(lhs_id) - expected_type := tc.assignment_expected_type(lhs_id, lhs_type) - tc.annotate_expected_expr(rhs_id, expected_type) + rhs_id := tc.a.child(&node, i + 1) + lhs_type := tc.resolve_lvalue_type(lhs_id) + expected_type := tc.assignment_expected_type(lhs_id, lhs_type) + tc.annotate_expected_expr(rhs_id, expected_type) i += 2 } } @@ -4320,42 +4319,91 @@ fn enum_backing_value_bounds(typ Type) ?EnumBackingValueBounds { 64 { 64 } else { 32 } } + if clean.props.has(.unsigned) { return match clean.size { 8 { - EnumBackingValueBounds{min: 0, max: 255, has_min: true, has_max: true, bits: bits} + EnumBackingValueBounds{ + min: 0 + max: 255 + has_min: true + has_max: true + bits: bits + } } 16 { - EnumBackingValueBounds{min: 0, max: 65535, has_min: true, has_max: true, bits: bits} + EnumBackingValueBounds{ + min: 0 + max: 65535 + has_min: true + has_max: true + bits: bits + } } else { - EnumBackingValueBounds{min: 0, has_min: true, bits: bits} + EnumBackingValueBounds{ + min: 0 + has_min: true + bits: bits + } } } } return match clean.size { 8 { - EnumBackingValueBounds{min: -128, max: 127, has_min: true, has_max: true, bits: bits} + EnumBackingValueBounds{ + min: -128 + max: 127 + has_min: true + has_max: true + bits: bits + } } 16 { - EnumBackingValueBounds{min: -32768, max: 32767, has_min: true, has_max: true, bits: bits} + EnumBackingValueBounds{ + min: -32768 + max: 32767 + has_min: true + has_max: true + bits: bits + } } 32, 0 { - EnumBackingValueBounds{min: -2147483647 - 1, max: 2147483647, has_min: true, has_max: true, bits: bits} + EnumBackingValueBounds{ + min: -2147483647 - 1 + max: 2147483647 + has_min: true + has_max: true + bits: bits + } } else { - EnumBackingValueBounds{bits: bits} + EnumBackingValueBounds{ + bits: bits + } } } } if clean is Rune { - return EnumBackingValueBounds{min: -2147483647 - 1, max: 2147483647, has_min: true, has_max: true, bits: 32} + return EnumBackingValueBounds{ + min: -2147483647 - 1 + max: 2147483647 + has_min: true + has_max: true + bits: 32 + } } if clean is USize { - return EnumBackingValueBounds{min: 0, has_min: true, bits: 64} + return EnumBackingValueBounds{ + min: 0 + has_min: true + bits: 64 + } } if clean is ISize { - return EnumBackingValueBounds{bits: 64} + return EnumBackingValueBounds{ + bits: 64 + } } return none } @@ -7927,13 +7975,13 @@ fn (mut tc TypeChecker) check_assign(id flat.NodeId, node flat.Node) { } $else { tc.check_node(rhs_id) } - rhs_type := tc.resolve_expr(rhs_id, expected_type) - if !tc.expr_compatible(rhs_id, rhs_type, expected_type) - && !tc.pointer_value_compatible(rhs_type, expected_type) - && !tc.pointer_arithmetic_assign_compatible(node.op, rhs_type, expected_type) { - tc.type_mismatch(.assignment_mismatch, - 'cannot assign `${rhs_type.name()}` to `${expected_type.name()}`', id) - } + rhs_type := tc.resolve_expr(rhs_id, expected_type) + if !tc.expr_compatible(rhs_id, rhs_type, expected_type) + && !tc.pointer_value_compatible(rhs_type, expected_type) + && !tc.pointer_arithmetic_assign_compatible(node.op, rhs_type, expected_type) { + tc.type_mismatch(.assignment_mismatch, + 'cannot assign `${rhs_type.name()}` to `${expected_type.name()}`', id) + } $if ownership ? { ownership_lhs_ids << lhs_id ownership_rhs_ids << rhs_id @@ -8220,19 +8268,19 @@ fn (mut tc TypeChecker) resolve_lvalue_type(lhs_id flat.NodeId) Type { } return tc.resolve_type(lhs_id) } - if lhs.kind == .index { - tc.check_index(lhs_id, lhs) - return tc.resolve_type(lhs_id) - } - if lhs.kind == .prefix && lhs.op == .mul && lhs.children_count > 0 { - inner := tc.resolve_type(tc.a.child(&lhs, 0)) - if inner is Pointer { - return inner.base_type - } - return inner - } + if lhs.kind == .index { + tc.check_index(lhs_id, lhs) return tc.resolve_type(lhs_id) } + if lhs.kind == .prefix && lhs.op == .mul && lhs.children_count > 0 { + inner := tc.resolve_type(tc.a.child(&lhs, 0)) + if inner is Pointer { + return inner.base_type + } + return inner + } + return tc.resolve_type(lhs_id) +} // check_return validates check return state for types. fn (mut tc TypeChecker) check_return(id flat.NodeId, node flat.Node) { @@ -9350,16 +9398,16 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI } if clean is Channel { match fn_node.value { - 'close' { - return CallInfo{ - name: 'chan.close' - params: tarr2(base_type, tc.parse_type('IError')) - return_type: Type(void_) - has_receiver: true - is_variadic: true - params_known: true - } + 'close' { + return CallInfo{ + name: 'chan.close' + params: tarr2(base_type, tc.parse_type('IError')) + return_type: Type(void_) + has_receiver: true + is_variadic: true + params_known: true } + } 'try_push' { return CallInfo{ name: 'chan.try_push' @@ -16330,12 +16378,12 @@ fn (tc &TypeChecker) interface_receiver_method_call_info(iface_name string, meth } call_name := '${iface_name}.${method}' return CallInfo{ - name: call_name - params: params + name: call_name + params: params shared_params: tc.fn_shared_params[decl_key] or { []bool{} } - return_type: tc.fn_ret_types[decl_key] or { Type(void_) } - has_receiver: true - params_known: true + return_type: tc.fn_ret_types[decl_key] or { Type(void_) } + has_receiver: true + params_known: true } } @@ -19438,13 +19486,13 @@ pub fn (tc &TypeChecker) resolve_generic_struct_method(type_name string, method } } return CallInfo{ - name: generic_key - params: sub_params + name: generic_key + params: sub_params shared_params: tc.fn_shared_params[generic_key] or { []bool{} } - return_type: sub_ret - has_receiver: true - is_variadic: tc.fn_variadic[generic_key] or { false } - params_known: true + return_type: sub_ret + has_receiver: true + is_variadic: tc.fn_variadic[generic_key] or { false } + params_known: true } } @@ -19506,13 +19554,13 @@ pub fn (tc &TypeChecker) resolve_generic_sum_method(type_name string, method str } } return CallInfo{ - name: generic_key - params: sub_params + name: generic_key + params: sub_params shared_params: tc.fn_shared_params[generic_key] or { []bool{} } - return_type: sub_ret - has_receiver: true - is_variadic: tc.fn_variadic[generic_key] or { false } - params_known: true + return_type: sub_ret + has_receiver: true + is_variadic: tc.fn_variadic[generic_key] or { false } + params_known: true } } diff --git a/vlib/v3/v3.v b/vlib/v3/v3.v index 330bff05694d19..4f7ee4af17fe4d 100644 --- a/vlib/v3/v3.v +++ b/vlib/v3/v3.v @@ -55,13 +55,13 @@ fn tcc_atomic_s_arg(prefs &pref.Preferences) string { return ' ${atomic_s}' } -fn prepare_c_flags_for_link(flags []string, c99 bool) ![]string { +fn prepare_c_flags_for_link(flags []string, c99 bool, pic_flag string) ![]string { support_flags := c_object_compile_support_flags(flags) mut prepared := []string{} for flag in flags { clean := flag.trim_space() if c_flag_is_object_file(clean) { - prepared << ensure_c_object_file(clean, support_flags, c99)! + prepared << ensure_c_object_file(clean, support_flags, c99, pic_flag)! } else { prepared << flag } @@ -97,7 +97,7 @@ fn c_flags_need_objective_c(flags []string) bool { return false } -fn ensure_c_object_file(obj_path string, support_flags []string, c99 bool) !string { +fn ensure_c_object_file(obj_path string, support_flags []string, c99 bool, pic_flag string) !string { if os.exists(obj_path) { return obj_path } @@ -107,13 +107,15 @@ fn ensure_c_object_file(obj_path string, support_flags []string, c99 bool) !stri cache_dir := os.join_path(os.vtmp_dir(), 'v3_thirdparty_objs') os.mkdir_all(cache_dir)! std_flag := if source_file.ends_with('.cpp') { '-std=c++11' } else { c_standard_flag(c99) } - cached_obj := os.join_path(cache_dir, c_object_cache_name(obj_path, support_flags, std_flag)) + cached_obj := os.join_path(cache_dir, c_object_cache_name(obj_path, support_flags, std_flag, + pic_flag)) if os.exists(cached_obj) && os.file_last_mod_unix(cached_obj) >= os.file_last_mod_unix(source_file) { return cached_obj } compiler := if source_file.ends_with('.cpp') { 'c++' } else { 'cc' } - cmd := '${compiler} ${std_flag} -w ${support_flags.join(' ')} -o ${os.quoted_path(cached_obj)} -c ${os.quoted_path(source_file)}' + pic_arg := if pic_flag.len > 0 { '${pic_flag} ' } else { '' } + cmd := '${compiler} ${std_flag} ${pic_arg}-w ${support_flags.join(' ')} -o ${os.quoted_path(cached_obj)} -c ${os.quoted_path(source_file)}' res := os.execute(cmd) if res.exit_code != 0 { return error('failed to build C object ${obj_path} from ${source_file}:\n${res.output}') @@ -132,12 +134,15 @@ fn c_source_from_object_file(obj_path string) ?string { return none } -fn c_object_cache_name(path string, support_flags []string, std_flag string) string { +fn c_object_cache_name(path string, support_flags []string, std_flag string, pic_flag string) string { base := path.replace_each(['/', '_', '\\', '_', ':', '_', '.', '_', ' ', '_']) // The compile flags (`-D`/`-I`/...) change the object contents, so they must // be part of the cache key; otherwise a rebuild with different `#flag` defines // silently links the stale object built with the previous configuration. mut cache_flags := [std_flag] + if pic_flag.len > 0 { + cache_flags << pic_flag + } cache_flags << support_flags return '${base}_${c_flags_hash(cache_flags)}.o' } @@ -163,6 +168,13 @@ fn c_standard_flag(c99 bool) string { return if c99 { '-std=c99' } else { '-std=gnu11' } } +fn shared_pic_flag(is_shared bool, target_os string) string { + if is_shared && target_os != 'windows' { + return '-fPIC' + } + return '' +} + fn run_test_binary(bin_file string) int { return run_binary(bin_file, []string{}) } @@ -212,6 +224,38 @@ fn input_is_cmd_v(input_file string) bool { || normalized.ends_with('/cmd/v/v.v') } +fn default_bin_file_for_input(input_file string) string { + if os.is_dir(input_file) { + base := os.base(os.real_path(input_file)) + if base.contains('.') { + return base.all_before('.') + } + return base + } + if input_file.ends_with('.v') { + return input_file.all_before_last('.v') + } + return input_file +} + +fn shared_library_postfix() string { + $if windows { + return '.dll' + } $else $if macos { + return '.dylib' + } $else { + return '.so' + } +} + +fn with_shared_library_postfix(path string) string { + postfix := shared_library_postfix() + if path.ends_with(postfix) { + return path + } + return path + postfix +} + // main runs the v3 entry point. fn main() { args := os.args[1..] @@ -225,6 +269,7 @@ fn main() { mut explicit_output := false mut backend := 'c' mut is_prod := false + mut is_shared := false mut is_strict := false mut is_selfhost := false mut no_parallel := false @@ -262,6 +307,9 @@ fn main() { } else if args[i] == '-prod' { is_prod = true i++ + } else if args[i] == '-shared' || args[i] == '--shared' { + is_shared = true + i++ } else if args[i] == '-selfhost' { is_selfhost = true i++ @@ -365,7 +413,10 @@ fn main() { mut bin_file := '' mut c_only := false if output_file == '' { - bin_file = input_file.all_before_last('.v') + bin_file = default_bin_file_for_input(input_file) + if is_shared { + bin_file = with_shared_library_postfix(bin_file) + } // The wasm backend writes the binary itself; default to .wasm. output_file = if backend == 'wasm' { bin_file + '.wasm' } else { bin_file + '.c' } } else if backend == 'wasm' { @@ -376,6 +427,9 @@ fn main() { bin_file = output_file.all_before_last('.c') } else { bin_file = output_file + if is_shared { + bin_file = with_shared_library_postfix(bin_file) + } output_file = bin_file + '.c' } @@ -612,12 +666,15 @@ fn main() { } opt_flag := if is_prod { '-O2 ' } else { '' } + shared_link_flag := if is_shared { '-shared ' } else { '' } + pic_flag := shared_pic_flag(is_shared, prefs.normalized_target_os()) + pic_arg := if pic_flag.len > 0 { '${pic_flag} ' } else { '' } warn_flags := if is_strict { '-Wall -Wextra -Werror=implicit-function-declaration -Wno-unused-variable -Wno-unused-parameter -Wno-int-conversion -Wno-missing-braces' } else { '-w' } - resolved_c_flags := prepare_c_flags_for_link(g.c_flags(), prefs.c99) or { + resolved_c_flags := prepare_c_flags_for_link(g.c_flags(), prefs.c99, pic_flag) or { eprintln(err.msg()) exit(1) } @@ -652,8 +709,8 @@ fn main() { tcc_includes := '-I${os.join_path_single(tcc_lib_dir, 'include')}' tcc_lib := '-L${tcc_lib_dir}' atomic_s_arg := tcc_atomic_s_arg(prefs) - cc_cmd = '${tcc_path} ${c_standard} ${tcc_includes} ${tcc_lib} ${warn_flags} -o ${bin_file} ${output_file}${atomic_s_arg} ${c_flags} -lm' - exec_cmd = 'cd ${cc_dir} && ${tcc_path} ${c_standard} ${tcc_includes} ${tcc_lib} ${warn_flags} -o out src.c${atomic_s_arg} ${c_flags} -lm' + cc_cmd = '${tcc_path} ${c_standard} ${pic_arg}${tcc_includes} ${tcc_lib} ${warn_flags} ${shared_link_flag}-o ${bin_file} ${output_file}${atomic_s_arg} ${c_flags} -lm' + exec_cmd = 'cd ${cc_dir} && ${tcc_path} ${c_standard} ${pic_arg}${tcc_includes} ${tcc_lib} ${warn_flags} ${shared_link_flag}-o out src.c${atomic_s_arg} ${c_flags} -lm' println(' > ${cc_cmd}') result = run_compile_command(exec_cmd) } @@ -661,17 +718,17 @@ fn main() { if result.exit_code != 0 && result.output.len > 0 { eprintln(' tcc error: ${result.output.trim_space()}') } - stack_flag := if prefs.normalized_target_os() == 'macos' { + stack_flag := if prefs.normalized_target_os() == 'macos' && !is_shared { ' -Wl,-stack_size,0x4000000' } else { '' } if needs_objective_c { - cc_cmd = 'cc ${c_standard} ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o ${bin_file} -x objective-c ${output_file} -x none ${c_flags} -lm' - exec_cmd = 'cd ${cc_dir} && cc ${c_standard} ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o out -x objective-c src.c -x none ${c_flags} -lm' + cc_cmd = 'cc ${c_standard} ${opt_flag}${pic_arg}${warn_flags} -Wno-int-conversion${stack_flag} ${shared_link_flag}-o ${bin_file} -x objective-c ${output_file} -x none ${c_flags} -lm' + exec_cmd = 'cd ${cc_dir} && cc ${c_standard} ${opt_flag}${pic_arg}${warn_flags} -Wno-int-conversion${stack_flag} ${shared_link_flag}-o out -x objective-c src.c -x none ${c_flags} -lm' } else { - cc_cmd = 'cc ${cc_lang_flag}${c_standard} ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o ${bin_file} ${output_file} ${c_flags} -lm' - exec_cmd = 'cd ${cc_dir} && cc ${cc_lang_flag}${c_standard} ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o out src.c ${c_flags} -lm' + cc_cmd = 'cc ${cc_lang_flag}${c_standard} ${opt_flag}${pic_arg}${warn_flags} -Wno-int-conversion${stack_flag} ${shared_link_flag}-o ${bin_file} ${output_file} ${c_flags} -lm' + exec_cmd = 'cd ${cc_dir} && cc ${cc_lang_flag}${c_standard} ${opt_flag}${pic_arg}${warn_flags} -Wno-int-conversion${stack_flag} ${shared_link_flag}-o out src.c ${c_flags} -lm' } println(' > ${cc_cmd}') result = run_compile_command(exec_cmd)