Files
agila_open_tool/tools/v/vlib/v3/v3.v
T

321 lines
7.9 KiB
V

module main
import os
import v3.bench
import v3.flat
import v3.gen.arm64
import v3.gen.c as cgen
import v3.markused
import v3.parser
import v3.pref
import v3.ssa
import v3.ssa.optimize
import v3.transform
import v3.types
fn run_compile_command(cmd string) os.Result {
exit_code := os.system(cmd)
return os.Result{
exit_code: exit_code
}
}
fn C.open(charptr, int, int) int
fn C.write(int, voidptr, int) int
fn C.close(int) int
fn C.chmod(charptr, int) int
const o_wronly_creat_trunc = 0x601 // O_WRONLY | O_CREAT | O_TRUNC on Darwin
fn main() {
args := os.args[1..]
if args.len == 0 {
eprintln('usage: v3 <file.v> [-o output|file.c] [-b c|arm64]')
exit(1)
}
mut input_file := ''
mut output_file := ''
mut backend := 'c'
mut is_prod := false
mut is_strict := false
mut no_parallel := false
mut i := 0
for i < args.len {
if args[i] == '-o' && i + 1 < args.len {
output_file = args[i + 1]
i += 2
} else if args[i] == '-b' && i + 1 < args.len {
backend = args[i + 1]
i += 2
} else if args[i] == '-prod' {
is_prod = true
i++
} else if args[i] == '-selfhost' {
i++
} else if args[i] == '-strict' {
is_strict = true
i++
} else if args[i] == '-no-parallel' || args[i] == '--no-parallel' {
no_parallel = true
i++
} else {
input_file = args[i]
i++
}
}
if input_file == '' {
eprintln('no input file')
exit(1)
}
mut bin_file := ''
mut c_only := false
if output_file == '' {
bin_file = input_file.all_before_last('.v')
output_file = bin_file + '.c'
} else if backend == 'c' && output_file.ends_with('.c') {
c_only = true
bin_file = output_file.all_before_last('.c')
} else {
bin_file = output_file
output_file = bin_file + '.c'
}
mut b := bench.new()
println('=== v3 benchmark ===')
// Parse directly to flat AST
mut prefs := pref.new_preferences()
prefs.backend = backend
prefs.vroot = resolve_vroot(prefs.vroot)
mut p := parser.Parser.new(prefs)
mut files := []string{}
builtin_dir := builtin_dir_for_vroot(prefs.vroot)
files << pref.get_v_files_from_dir(builtin_dir, prefs.user_defines, prefs.target_os)
p.parse_files(files)
mut a := p.a
a.user_code_start = a.nodes.len
// Parse user input: single file or directory
mut user_files := []string{}
if input_file.ends_with('.v') {
user_files << input_file
} else if os.is_dir(input_file) {
user_files = pref.get_v_files_from_dir(input_file, prefs.user_defines, prefs.target_os)
} else {
user_files << input_file
}
for uf in user_files {
p.parse_into(uf)
}
// Resolve imports recursively
resolve_imports(mut a, mut p, prefs, user_files)
b.step('parse')
// Type-collect + check BEFORE transform, so the transformer is type-aware
// (like v2: check runs before transform). The transformer reads cached
// per-expression types for type-dependent lowering.
mut pre_tc := types.TypeChecker.new(a)
pre_tc.collect(a)
pre_tc.diagnose_unknown_calls = true
for uf in user_files {
pre_tc.diagnostic_files[uf] = true
}
pre_tc.check_semantics()
if pre_tc.errors.len > 0 {
print_type_errors(pre_tc.errors)
exit(1)
}
b.step('check')
// Mark used functions (dead-code elimination). This is done before transform
// so the transformer can skip function bodies that the C backend will prune.
used_fns := markused.mark_used(a, pre_tc)
b.step('markused')
// Transform (match lowering, string/in lowering, etc.)
transform.transform_with_used(mut a, &pre_tc, used_fns)
b.step('transform')
// Reuse the pre-transform checker for metadata only. Transform does not add
// declarations, and v1/v2 do not run a second semantic checker after lowering.
pre_tc.diagnose_unknown_calls = false
pre_tc.reject_unlowered_map_mutation = true
pre_tc.annotate_types()
for uf in user_files {
pre_tc.diagnostic_files[uf] = true
}
b.step('annotate types')
if backend == 'arm64' {
// SSA + ARM64 native backend
mut m := ssa.build_with_used(a, used_fns, pre_tc)
b.step('ssa build')
if is_prod {
optimize.optimize(mut m)
b.step('optimize')
}
mut g := arm64.Gen.new(m)
g.gen()
b.step('arm64 gen')
g.write_and_link(bin_file)
b.step('link')
} else {
// C backend (default)
mut g := cgen.FlatGen.new()
c_code := g.gen_with_used_options(a, used_fns, &pre_tc, no_parallel)
if !write_text_file_raw(output_file, c_code) {
eprintln('error writing ${output_file}')
exit(1)
}
gen_step_name := if g.was_parallel() { 'gen C/write (parallel)' } else { 'gen C/write' }
b.step(gen_step_name)
if c_only {
b.print_report()
return
}
opt_flag := if is_prod { '-O2 ' } 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'
}
mut cc_cmd := ''
mut result := os.Result{}
if !is_prod {
tcc_dir := os.join_path_single(os.join_path_single(prefs.vroot, 'thirdparty'), 'tcc')
tcc_path := os.join_path_single(tcc_dir, 'tcc.exe')
tcc_lib_dir := os.join_path_single(tcc_dir, 'lib')
tcc_includes := '-I${os.join_path_single(tcc_lib_dir, 'include')}'
tcc_lib := '-L${tcc_lib_dir}'
cc_cmd = '${tcc_path} ${tcc_includes} ${tcc_lib} ${warn_flags} -o ${bin_file} ${output_file} -lm'
println(' > ${cc_cmd}')
result = run_compile_command(cc_cmd)
}
if is_prod || result.exit_code != 0 {
if result.exit_code != 0 && result.output.len > 0 {
eprintln(' tcc error: ${result.output.trim_space()}')
}
cc_cmd = 'cc -std=gnu11 ${opt_flag}${warn_flags} -Wno-int-conversion -Wl,-stack_size,0x4000000 -o ${bin_file} ${output_file} -lm'
println(' > ${cc_cmd}')
result = run_compile_command(cc_cmd)
if result.exit_code != 0 {
eprintln('C compilation failed:')
eprintln(result.output)
exit(1)
}
}
b.step('cc')
}
b.print_report()
}
fn resolve_vroot(initial string) string {
if is_valid_vroot(initial) {
return initial
}
mut dir := os.getwd()
for _ in 0 .. 8 {
if is_valid_vroot(dir) {
return dir
}
parent := os.dir(dir)
if parent == dir {
break
}
dir = parent
}
return initial
}
fn is_valid_vroot(root string) bool {
return root.len > 0 && os.is_dir(builtin_dir_for_vroot(root))
}
fn builtin_dir_for_vroot(root string) string {
return os.join_path_single(os.join_path_single(root, 'vlib'), 'builtin')
}
fn write_text_file_raw(path string, data string) bool {
fd := C.open(path.str, o_wronly_creat_trunc, 420)
if fd < 0 {
return false
}
if data.len > 0 {
written := C.write(fd, data.str, data.len)
if written != data.len {
C.close(fd)
return false
}
}
if C.close(fd) != 0 {
return false
}
return C.chmod(path.str, 420) == 0
}
fn print_type_errors(errors []types.TypeError) {
eprintln('type checker found ${errors.len} error(s):')
max_errors := if errors.len < 20 { errors.len } else { 20 }
for ei in 0 .. max_errors {
eprintln(' ${errors[ei].msg}')
}
if errors.len > 20 {
eprintln(' ... and ${errors.len - 20} more')
}
}
fn resolve_imports(mut a flat.FlatAst, mut p parser.Parser, prefs &pref.Preferences, initial_files []string) {
mut parsed_modules := map[string]bool{}
parsed_modules['builtin'] = true
parsed_modules['main'] = true
mut first_file := ''
if initial_files.len > 0 {
first_file = initial_files[0]
}
mut changed := true
for changed {
changed = false
mut cur_file := first_file
scan_len := a.nodes.len
for node_idx in 0 .. scan_len {
node := a.nodes[node_idx]
if node.kind == .file && node.value.len > 0 {
cur_file = node.value
continue
}
if node.kind != .import_decl {
continue
}
mod_name := node.value
if mod_name in parsed_modules {
continue
}
parsed_modules[mod_name] = true
changed = true
importing_file := if cur_file.len > 0 { cur_file } else { first_file }
mod_dir := prefs.get_module_path(mod_name, importing_file)
if mod_dir == '' || !os.is_dir(mod_dir) {
continue
}
mod_files := pref.get_v_files_from_dir(mod_dir, prefs.user_defines, prefs.target_os)
for mf in mod_files {
p.parse_into(mf)
}
}
}
}