321 lines
7.9 KiB
V
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)
|
|
}
|
|
}
|
|
}
|
|
}
|