From fafb035fb523fcb3105e112e1142a640b7f21b44 Mon Sep 17 00:00:00 2001 From: crthpl <56052645+crthpl@users.noreply.github.com> Date: Tue, 16 Mar 2021 17:43:17 -0700 Subject: [PATCH] all: reimplement inline assembly (#8645) --- cmd/tools/modules/testing/common.v | 5 - cmd/tools/vtest.v | 94 ++- doc/docs.md | 28 +- examples/asm.v | 16 + examples/fibonacci.v | 22 +- vlib/builtin/bare/linuxsys_bare.v | 383 ++++++----- vlib/v/ast/ast.v | 283 +++++++- vlib/v/checker/checker.v | 131 +++- .../tests/asm_alias_does_not_exist.out | 5 + .../checker/tests/asm_alias_does_not_exist.vv | 3 + vlib/v/checker/tests/asm_immutable_err.out | 7 + vlib/v/checker/tests/asm_immutable_err.vv | 16 + vlib/v/compiler_errors_test.v | 4 +- vlib/v/fmt/fmt.v | 152 ++++- vlib/v/gen/c/cgen.v | 180 ++++- vlib/v/gen/js/js.v | 3 + vlib/v/markused/walker.v | 16 +- vlib/v/parser/parser.v | 645 ++++++++++++++++-- vlib/v/parser/pratt.v | 20 +- vlib/v/parser/v_parser_test.v | 1 + vlib/v/pref/default.v | 1 + vlib/v/pref/pref.v | 74 +- vlib/v/pref/should_compile.v | 26 +- vlib/v/table/table.v | 35 + vlib/v/table/types.v | 33 + vlib/v/tests/asm_test.v | 29 - vlib/v/tests/assembly/asm_test.amd64.v | 99 +++ vlib/v/tests/assembly/asm_test.i386.v | 98 +++ .../assembly/util/dot_amd64_util.amd64.v | 15 + 29 files changed, 2089 insertions(+), 335 deletions(-) create mode 100644 examples/asm.v create mode 100644 vlib/v/checker/tests/asm_alias_does_not_exist.out create mode 100644 vlib/v/checker/tests/asm_alias_does_not_exist.vv create mode 100644 vlib/v/checker/tests/asm_immutable_err.out create mode 100644 vlib/v/checker/tests/asm_immutable_err.vv delete mode 100644 vlib/v/tests/asm_test.v create mode 100644 vlib/v/tests/assembly/asm_test.amd64.v create mode 100644 vlib/v/tests/assembly/asm_test.i386.v create mode 100644 vlib/v/tests/assembly/util/dot_amd64_util.amd64.v diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index 70fdeb6e0d..fdeb768d0a 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -196,11 +196,6 @@ pub fn (mut ts TestSession) test() { continue } } - $if tinyc { - if file.contains('asm') { - continue - } - } remaining_files << dot_relative_file } remaining_files = vtest.filter_vtest_only(remaining_files, fix_slashes: false) diff --git a/cmd/tools/vtest.v b/cmd/tools/vtest.v index 727c6da2de..416ff66f3b 100644 --- a/cmd/tools/vtest.v +++ b/cmd/tools/vtest.v @@ -3,6 +3,7 @@ module main import os import os.cmdline import testing +import v.pref fn main() { args := os.args.clone() @@ -18,20 +19,35 @@ fn main() { eprintln('Use `v test-all` instead.') exit(1) } + backend_pos := args_before.index('-b') + backend := if backend_pos == -1 { '.c' } else { args_before[backend_pos + 1] } // this giant mess because closures are not implemented + mut ts := testing.new_test_session(args_before.join(' ')) for targ in args_after { - if os.exists(targ) && targ.ends_with('_test.v') { - ts.files << targ - continue - } if os.is_dir(targ) { // Fetch all tests from the directory - ts.files << os.walk_ext(targ.trim_right(os.path_separator), '_test.v') + files, skip_files := should_test_dir(targ.trim_right(os.path_separator), backend) + ts.files << files + ts.skip_files << skip_files continue + } else if os.exists(targ) { + match should_test(targ, backend) { + .test { + ts.files << targ + continue + } + .skip { + ts.files << targ + ts.skip_files << targ + continue + } + .ignore {} + } + } else { + eprintln('\nUnrecognized test file `$targ`.\n `v test` can only be used with folders and/or _test.v files.\n') + show_usage() + exit(1) } - eprintln('\nUnrecognized test file `$targ` .\n `v test` can only be used with folders and/or _test.v files.\n') - show_usage() - exit(1) } testing.header('Testing...') ts.test() @@ -52,3 +68,65 @@ fn show_usage() { println(' NB: you can also give many and mixed folder/ file_test.v arguments after `v test` .') println('') } + +pub fn should_test_dir(path string, backend string) ([]string, []string) { // return is (files, skip_files) + mut files := os.ls(path) or { return []string{}, []string{} } + mut local_path_separator := os.path_separator + if path.ends_with(os.path_separator) { + local_path_separator = '' + } + mut res_files := []string{} + mut skip_files := []string{} + for file in files { + p := path + local_path_separator + file + if os.is_dir(p) && !os.is_link(p) { + ret_files, ret_skip_files := should_test_dir(p, backend) + res_files << ret_files + skip_files << ret_skip_files + } else if os.exists(p) { + match should_test(p, backend) { + .test { + res_files << p + } + .skip { + res_files << p + skip_files << p + } + .ignore {} + } + } + } + return res_files, skip_files +} + +enum ShouldTestStatus { + test // do test + skip + ignore +} + +fn should_test(path string, backend string) ShouldTestStatus { + if path.ends_with('_test.v') { + return .test + } + if path.ends_with('.v') && path.count('.') == 2 { + if !path.all_before_last('.v').all_before_last('.').ends_with('_test') { + return .ignore + } + backend_arg := path.all_before_last('.v').all_after_last('.') + arch := pref.arch_from_string(backend_arg) or { pref.Arch._auto } + if arch == pref.get_host_arch() { + return .test + } else if arch == ._auto { + if backend_arg == 'c' { // .c.v + return if backend == 'c' { ShouldTestStatus.test } else { ShouldTestStatus.skip } + } + if backend_arg == 'js' { + return if backend == 'js' { ShouldTestStatus.test } else { ShouldTestStatus.skip } + } + } else { + return .skip + } + } + return .ignore +} diff --git a/doc/docs.md b/doc/docs.md index 8d13fe0c32..bbfe49dd3f 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3848,20 +3848,26 @@ To improve safety and maintainability, operator overloading is limited: are auto generated when the operators are defined though they must return the same type. ## Inline assembly - -TODO: not implemented yet - -```v failcompile -fn main() { - a := 10 - asm x64 { - mov eax, [a] - add eax, 10 - mov [a], eax - } + +```v ignore +a := 100 +b := 20 +mut c := 0 +asm amd64 { + mov eax, a + add eax, b + mov c, eax + ; =r (c) as c // output + ; r (a) as a // input + r (b) as b } +println('a: $a') // 100 +println('b: $b') // 20 +println('c: $c') // 120 ``` +For more examples, see [github.com/vlang/v/tree/master/vlib/v/tests/assembly/asm_test.amd64.v](https://github.com/vlang/v/tree/master/vlib/v/tests/assembly/asm_test.amd64.v) + ## Translating C to V TODO: translating C to V will be available in V 0.3. diff --git a/examples/asm.v b/examples/asm.v new file mode 100644 index 0000000000..258d400f5b --- /dev/null +++ b/examples/asm.v @@ -0,0 +1,16 @@ +fn main() { + a := 100 + b := 20 + mut c := 0 + asm amd64 { + mov eax, a + add eax, b + mov c, eax + ; =r (c) // output + ; r (a) // input + r (b) + } + println('a: $a') // 100 + println('b: $b') // 20 + println('c: $c') // 120 +} diff --git a/examples/fibonacci.v b/examples/fibonacci.v index 6244c96092..52f95b730c 100644 --- a/examples/fibonacci.v +++ b/examples/fibonacci.v @@ -1,18 +1,18 @@ // This program displays the fibonacci sequence -import os +// import os fn main() { // Check for user input - if os.args.len != 2 { - println('usage: fibonacci [rank]') +//if os.args.len != 2 { +// println('usage: fibonacci [rank]') // Exit - return - } +// return +// } // Parse first argument and cast it to int - stop := os.args[1].int() - +// stop := os.args[1].int() + stop := 23 // Can only calculate correctly until rank 92 if stop > 92 { println('rank must be 92 or less') @@ -20,10 +20,10 @@ fn main() { } // Three consecutive terms of the sequence - mut a := u64(0) - mut b := u64(0) - mut c := u64(1) - + mut a := 0 + mut b := 0 + mut c := 1 + println(a+c+c) for _ in 0 .. stop { // Set a and b to the next term a = b diff --git a/vlib/builtin/bare/linuxsys_bare.v b/vlib/builtin/bare/linuxsys_bare.v index 9096685383..c174f9d63c 100644 --- a/vlib/builtin/bare/linuxsys_bare.v +++ b/vlib/builtin/bare/linuxsys_bare.v @@ -4,17 +4,17 @@ pub enum Linux_mem { page_size = 4096 } -pub enum Wp_sys { - wnohang = 0x00000001 - wuntraced = 0x00000002 - wstopped = 0x00000002 - wexited = 0x00000004 - wcontinued = 0x00000008 - wnowait = 0x01000000 // don't reap, just poll status. - __wnothread = 0x20000000 // don't wait on children of other threads in this group - __wall = 0x40000000 // wait on all children, regardless of type - __wclone = 0x80000000 // wait only on non-sigchld children -} +pub const ( + wp_sys_wnohang = u64(0x00000001) + wp_sys_wuntraced = u64(0x00000002) + wp_sys_wstopped = u64(0x00000002) + wp_sys_wexited = u64(0x00000004) + wp_sys_wcontinued = u64(0x00000008) + wp_sys_wnowait = u64(0x01000000) // don't reap, just poll status. + wp_sys___wnothread = u64(0x20000000) // don't wait on children of other threads in this group + wp_sys___wall = u64(0x40000000) // wait on all children, regardless of type + wp_sys___wclone = u64(0x80000000) // wait only on non-sigchld children +) // First argument to waitid: pub enum Wi_which { @@ -32,7 +32,8 @@ pub enum Wi_si_code { cld_continued = 6 // stopped child has continued } -/* Paraphrased from "man 2 waitid" on Linux +/* +Paraphrased from "man 2 waitid" on Linux Upon successful return, waitid() fills in the following fields of the siginfo_t structure @@ -74,98 +75,96 @@ pub enum Sig_index { } pub enum Signo { - sighup = 1 // Hangup. - sigint = 2 // Interactive attention signal. - sigquit = 3 // Quit. - sigill = 4 // Illegal instruction. - sigtrap = 5 // Trace/breakpoint trap. - sigabrt = 6 // Abnormal termination. + sighup = 1 // Hangup. + sigint = 2 // Interactive attention signal. + sigquit = 3 // Quit. + sigill = 4 // Illegal instruction. + sigtrap = 5 // Trace/breakpoint trap. + sigabrt = 6 // Abnormal termination. sigbus = 7 - sigfpe = 8 // Erroneous arithmetic operation. - sigkill = 9 // Killed. + sigfpe = 8 // Erroneous arithmetic operation. + sigkill = 9 // Killed. sigusr1 = 10 - sigsegv = 11 // Invalid access to storage. + sigsegv = 11 // Invalid access to storage. sigusr2 = 12 - sigpipe = 13 // Broken pipe. - sigalrm = 14 // Alarm clock. - sigterm = 15 // Termination request. + sigpipe = 13 // Broken pipe. + sigalrm = 14 // Alarm clock. + sigterm = 15 // Termination request. sigstkflt = 16 sigchld = 17 sigcont = 18 sigstop = 19 sigtstp = 20 - sigttin = 21 // Background read from control terminal. - sigttou = 22 // Background write to control terminal. + sigttin = 21 // Background read from control terminal. + sigttou = 22 // Background write to control terminal. sigurg = 23 - sigxcpu = 24 // CPU time limit exceeded. - sigxfsz = 25 // File size limit exceeded. - sigvtalrm = 26 // Virtual timer expired. - sigprof = 27 // Profiling timer expired. + sigxcpu = 24 // CPU time limit exceeded. + sigxfsz = 25 // File size limit exceeded. + sigvtalrm = 26 // Virtual timer expired. + sigprof = 27 // Profiling timer expired. sigwinch = 28 sigpoll = 29 sigsys = 31 } - -pub enum Fcntl { - fd_cloexec = 0x00000001 - f_dupfd = 0x00000000 - f_exlck = 0x00000004 - f_getfd = 0x00000001 - f_getfl = 0x00000003 - f_getlk = 0x00000005 - f_getlk64 = 0x0000000c - f_getown = 0x00000009 - f_getowner_uids = 0x00000011 - f_getown_ex = 0x00000010 - f_getsig = 0x0000000b - f_ofd_getlk = 0x00000024 - f_ofd_setlk = 0x00000025 - f_ofd_setlkw = 0x00000026 - f_owner_pgrp = 0x00000002 - f_owner_pid = 0x00000001 - f_owner_tid = 0x00000000 - f_rdlck = 0x00000000 - f_setfd = 0x00000002 - f_setfl = 0x00000004 - f_setlk = 0x00000006 - f_setlk64 = 0x0000000d - f_setlkw = 0x00000007 - f_setlkw64 = 0x0000000e - f_setown = 0x00000008 - f_setown_ex = 0x0000000f - f_setsig = 0x0000000a - f_shlck = 0x00000008 - f_unlck = 0x00000002 - f_wrlck = 0x00000001 - lock_ex = 0x00000002 - lock_mand = 0x00000020 - lock_nb = 0x00000004 - lock_read = 0x00000040 - lock_rw = 0x000000c0 - lock_sh = 0x00000001 - lock_un = 0x00000008 - lock_write = 0x00000080 - o_accmode = 0x00000003 - o_append = 0x00000400 - o_cloexec = 0x00080000 - o_creat = 0x00000040 - o_direct = 0x00004000 - o_directory = 0x00010000 - o_dsync = 0x00001000 - o_excl = 0x00000080 - o_largefile = 0x00008000 - o_ndelay = 0x00000800 - o_noatime = 0x00040000 - o_noctty = 0x00000100 - o_nofollow = 0x00020000 - o_nonblock = 0x00000800 - o_path = 0x00200000 - o_rdonly = 0x00000000 - o_rdwr = 0x00000002 - o_trunc = 0x00000200 - o_wronly = 0x00000001 -} +pub const ( + fcntlf_dupfd = 0x00000000 + fcntlf_exlck = 0x00000004 + fcntlf_getfd = 0x00000001 + fcntlf_getfl = 0x00000003 + fcntlf_getlk = 0x00000005 + fcntlf_getlk64 = 0x0000000c + fcntlf_getown = 0x00000009 + fcntlf_getowner_uids = 0x00000011 + fcntlf_getown_ex = 0x00000010 + fcntlf_getsig = 0x0000000b + fcntlf_ofd_getlk = 0x00000024 + fcntlf_ofd_setlk = 0x00000025 + fcntlf_ofd_setlkw = 0x00000026 + fcntlf_owner_pgrp = 0x00000002 + fcntlf_owner_pid = 0x00000001 + fcntlf_owner_tid = 0x00000000 + fcntlf_rdlck = 0x00000000 + fcntlf_setfd = 0x00000002 + fcntlf_setfl = 0x00000004 + fcntlf_setlk = 0x00000006 + fcntlf_setlk64 = 0x0000000d + fcntlf_setlkw = 0x00000007 + fcntlf_setlkw64 = 0x0000000e + fcntlf_setown = 0x00000008 + fcntlf_setown_ex = 0x0000000f + fcntlf_setsig = 0x0000000a + fcntlf_shlck = 0x00000008 + fcntlf_unlck = 0x00000002 + fcntlf_wrlck = 0x00000001 + fcntllock_ex = 0x00000002 + fcntllock_mand = 0x00000020 + fcntllock_nb = 0x00000004 + fcntllock_read = 0x00000040 + fcntllock_rw = 0x000000c0 + fcntllock_sh = 0x00000001 + fcntllock_un = 0x00000008 + fcntllock_write = 0x00000080 + fcntlo_accmode = 0x00000003 + fcntlo_append = 0x00000400 + fcntlo_cloexec = 0x00080000 + fcntlo_creat = 0x00000040 + fcntlo_direct = 0x00004000 + fcntlo_directory = 0x00010000 + fcntlo_dsync = 0x00001000 + fcntlo_excl = 0x00000080 + fcntlo_largefile = 0x00008000 + fcntlo_ndelay = 0x00000800 + fcntlo_noatime = 0x00040000 + fcntlo_noctty = 0x00000100 + fcntlo_nofollow = 0x00020000 + fcntlo_nonblock = 0x00000800 + fcntlo_path = 0x00200000 + fcntlo_rdonly = 0x00000000 + fcntlo_rdwr = 0x00000002 + fcntlo_trunc = 0x00000200 + fcntlo_wronly = 0x00000001 +) pub enum Errno { enoerror = 0x00000000 @@ -222,104 +221,106 @@ pub enum Map_flags { map_fixed = 0x10 map_file = 0x00 map_anonymous = 0x20 - map_anon = 0x20 map_huge_shift = 26 map_huge_mask = 0x3f } -fn do_not_call_me_asm_keeper0() { - unsafe { - asm { - "\n" - "ret\n" - "" - ".intel_syntax noprefix\n" - ".globl _start, sys_call0\n" - ".globl sys_call1, sys_call2, sys_call3\n" - ".globl sys_call4, sys_call5, sys_call6\n" - "" - "_start:\n" - "xor rbp,rbp\n" - "pop rdi\n" - "mov rsi,rsp\n" - "and rsp,-16\n" - "call main\n" - "mov rdi,rax\n" /* syscall param 1 = rax (ret value of main) */ - "mov rax,60\n" /* SYS_exit */ - "syscall\n" - "" - // should never be reached, but if the OS somehow fails to kill us, - // it will cause a segmentation fault - "ret\n" - "sys_call0:\n" - "mov rax,rdi\n" - "syscall\n" - "ret\n" - "" - "sys_call1:\n" - "mov rax,rdi\n" - "mov rdi,rsi\n" - "syscall\n" - "ret\n" - "" - "sys_call2:\n" - "mov rax,rdi\n" - "mov rdi,rsi\n" - "mov rsi,rdx\n" - "syscall\n" - "ret\n" - "" - "sys_call3:\n" - "mov rax,rdi\n" - "mov rdi,rsi\n" - "mov rsi,rdx\n" - "mov rdx,rcx\n" - "syscall\n" - "ret\n" - "" - "sys_call4:\n" - "mov rax,rdi\n" - "mov rdi,rsi\n" - "mov rsi,rdx\n" - "mov rdx,rcx\n" - "mov r10,r8\n" - "syscall\n" - "ret\n" - "" - "sys_call5:\n" - "mov rax,rdi\n" - "mov rdi,rsi\n" - "mov rsi,rdx\n" - "mov rdx,rcx\n" - "mov r10,r8\n" - "mov r8,r9\n" - "syscall\n" - "ret\n" - "" - "sys_call6:\n" - "mov rax,rdi\n" - "mov rdi,rsi\n" - "mov rsi,rdx\n" - "mov rdx,rcx\n" - "mov r10,r8\n" - "mov r8,r9\n" - "mov r9, [rsp+8]\n" - "syscall\n" - "ret\n" - "" - ".att_syntax \n" - } +fn sys_call0(scn u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) } + return res } -fn sys_call0(scn u64) u64 -fn sys_call1(scn, arg1 u64) u64 -fn sys_call2(scn, arg1, arg2 u64) u64 -fn sys_call3(scn, arg1, arg2, arg3 u64) u64 -fn sys_call4(scn, arg1, arg2, arg3, arg4 u64) u64 -fn sys_call5(scn, arg1, arg2, arg3, arg4, arg5 u64) u64 -fn sys_call6(scn, arg1, arg2, arg3, arg4, arg5, arg6 u64) u64 +fn sys_call1(scn u64, arg1 u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + } + return res +} +fn sys_call2(scn u64, arg1 u64, arg2 u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + } + return res +} +fn sys_call3(scn u64, arg1 u64, arg2 u64, arg3 u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + } + return res +} +fn sys_call4(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64) u64 { + res := u64(0) + asm amd64 { + mov r10, arg4 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + ; r10 + } + return res +} + +fn sys_call5(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64) u64 { + res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + r (arg5) + ; r10 r8 + } + return res +} +fn sys_call6(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64, arg6 u64) u64 { + res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + mov r9, arg6 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + r (arg5) + r (arg6) + ; r10 r8 r9 + } + return res +} fn split_int_errno(rc_in u64) (i64, Errno) { rc := i64(rc_in) @@ -330,7 +331,7 @@ fn split_int_errno(rc_in u64) (i64, Errno) { } // 0 sys_read unsigned int fd char *buf size_t count -pub fn sys_read (fd i64, buf byteptr, count u64) (i64, Errno) { +pub fn sys_read(fd i64, buf byteptr, count u64) (i64, Errno) { return split_int_errno(sys_call3(0, u64(fd), u64(buf), count)) } @@ -339,8 +340,8 @@ pub fn sys_write(fd i64, buf byteptr, count u64) (i64, Errno) { return split_int_errno(sys_call3(1, u64(fd), u64(buf), count)) } -pub fn sys_open(filename byteptr, flags Fcntl, mode int) (i64, Errno) { - //2 sys_open const char *filename int flags int mode +pub fn sys_open(filename byteptr, flags i64, mode int) (i64, Errno) { + // 2 sys_open const char *filename int flags int mode return split_int_errno(sys_call3(2, u64(filename), u64(flags), u64(mode))) } @@ -392,19 +393,17 @@ pub fn sys_vfork() int { } // 33 sys_dup2 unsigned int oldfd unsigned int newfd -pub fn sys_dup2 (oldfd, newfd int) (i64, Errno) { - return split_int_errno(sys_call2(33, u64(oldfd),u64(newfd))) +pub fn sys_dup2(oldfd int, newfd int) (i64, Errno) { + return split_int_errno(sys_call2(33, u64(oldfd), u64(newfd))) } - -//59 sys_execve const char *filename const char *const argv[] const char *const envp[] -//pub fn sys_execve(filename byteptr, argv []byteptr, envp []byteptr) int { +// 59 sys_execve const char *filename const char *const argv[] const char *const envp[] +// pub fn sys_execve(filename byteptr, argv []byteptr, envp []byteptr) int { // return sys_call3(59, filename, argv, envp) //} - // 60 sys_exit int error_code -pub fn sys_exit (ec int) { +pub fn sys_exit(ec int) { sys_call1(60, u64(ec)) } @@ -414,12 +413,10 @@ pub fn sys_getuid() int { } // 247 sys_waitid int which pid_t upid struct siginfo *infop int options struct rusage *ru -pub fn sys_waitid (which Wi_which, pid int, infop &int, options Wp_sys, ru voidptr) Errno { +pub fn sys_waitid(which Wi_which, pid int, infop &int, options int, ru voidptr) Errno { return Errno(sys_call5(247, u64(which), u64(pid), u64(infop), u64(options), u64(ru))) } - - /* A few years old, but still relevant https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index c6eb25a02f..bb66065e5b 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -6,6 +6,7 @@ module ast import v.token import v.table import v.errors +import v.pref pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl @@ -17,14 +18,14 @@ pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type | TypeOf | UnsafeExpr -pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt | - EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt | - GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | SqlStmt | - StructDecl | TypeDecl +pub type Stmt = AsmStmt | AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | + DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | + GoStmt | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | + SqlStmt | StructDecl | TypeDecl // NB: when you add a new Expr or Stmt type with a .pos field, remember to update // the .position() token.Position methods too. -pub type ScopeObject = ConstField | GlobalField | Var +pub type ScopeObject = AsmRegister | ConstField | GlobalField | Var // TOOD: replace table.Param pub type Node = ConstField | EnumField | Expr | Field | File | GlobalField | IfBranch | @@ -1032,6 +1033,175 @@ pub mut: in_prexpr bool // is the parent node an ast.PrefixExpr } +pub struct AsmStmt { +pub: + arch pref.Arch + is_top_level bool + is_volatile bool + is_goto bool + clobbered []AsmClobbered + pos token.Position +pub mut: + templates []AsmTemplate + scope &Scope + output []AsmIO + input []AsmIO + global_labels []string // listed after clobbers, paired with is_goto == true + local_labels []string // local to the assembly block + exported_symbols []string // functions defined in assembly block, exported with `.globl` +} + +pub struct AsmTemplate { +pub mut: + name string + is_label bool // `example_label:` + is_directive bool // .globl assembly_function + args []AsmArg + comments []Comment + pos token.Position +} + +// [eax+5] | j | eax | true | `a` | 0.594 | 123 | 'hi' | label_name +pub type AsmArg = AsmAddressing | AsmAlias | AsmRegister | BoolLiteral | CharLiteral | + FloatLiteral | IntegerLiteral | string + +pub struct AsmRegister { +pub: + name string // eax or r12d +mut: + typ table.Type + size int +} + +pub struct AsmAlias { +pub: + name string // a + pos token.Position +} + +pub struct AsmAddressing { +pub: + displacement u32 // 8, 16 or 32 bit literal value + scale int = -1 // 1, 2, 4, or 8 literal + mode AddressingMode + pos token.Position +pub mut: + base AsmArg // gpr + index AsmArg // gpr +} + +// adressing modes: +pub enum AddressingMode { + invalid + displacement // displacement + base // base + base_plus_displacement // base + displacement + index_times_scale_plus_displacement // (index ∗ scale) + displacement + base_plus_index_plus_displacement // base + (index ∗ scale) + displacement + base_plus_index_times_scale_plus_displacement // base + index + displacement + rip_plus_displacement // rip + displacement +} + +pub struct AsmClobbered { +pub: + reg AsmRegister +pub mut: + comments []Comment +} + +// : [alias_a] '=r' (a) // this is a comment +pub struct AsmIO { +pub: + alias string // [alias_a] + constraint string // '=r' + expr Expr // (a) + comments []Comment // // this is a comment + typ table.Type + pos token.Position +} + +pub const ( + // reference: https://en.wikipedia.org/wiki/X86#/media/File:Table_of_x86_Registers_svg.svg + // map register size -> register name + x86_no_number_register_list = map{ + 8: ['al', 'ah', 'bl', 'bh', 'cl', 'ch', 'dl', 'dh', 'bpl', 'sil', 'dil', 'spl'] + 16: ['ax', 'bx', 'cx', 'dx', 'bp', 'si', 'di', 'sp', /* segment registers */ 'cs', 'ss', + 'ds', 'es', 'fs', 'gs', 'flags', 'ip', /* task registers */ 'gdtr', 'idtr', 'tr', 'ldtr', + // CSR register 'msw', /* FP core registers */ 'cw', 'sw', 'tw', 'fp_ip', 'fp_dp', + 'fp_cs', 'fp_ds', 'fp_opc'] + 32: [ + 'eax', + 'ebx', + 'ecx', + 'edx', + 'ebp', + 'esi', + 'edi', + 'esp', + 'eflags', + 'eip', /* CSR register */ + 'mxcsr' /* 32-bit FP core registers 'fp_dp', 'fp_ip' (TODO: why are there duplicates?) */, + ] + 64: ['rax', 'rbx', 'rcx', 'rdx', 'rbp', 'rsi', 'rdi', 'rsp', 'rflags', 'rip'] + } + // no comments because maps do not support comments + // r#*: gp registers added in 64-bit extensions, can only be from 8-15 actually + // *mm#: vector/simd registors + // st#: floating point numbers + // cr#: control/status registers + // dr#: debug registers + x86_with_number_register_list = map{ + 8: map{ + 'r#b': 16 + } + 16: map{ + 'r#w': 16 + } + 32: map{ + 'r#d': 16 + } + 64: map{ + 'r#': 16 + 'mm#': 16 + 'cr#': 16 + 'dr#': 16 + } + 80: map{ + 'st#': 16 + } + 128: map{ + 'xmm#': 32 + } + 256: map{ + 'ymm#': 32 + } + 512: map{ + 'zmm#': 32 + } + } +) + +// TODO: saved priviled registers for arm +const ( + arm_no_number_register_list = ['fp' /* aka r11 */, /* not instruction pointer: */ 'ip' /* aka r12 */, + 'sp' /* aka r13 */, 'lr' /* aka r14 */, /* this is instruction pointer ('program counter'): */ + 'pc' /* aka r15 */, + ] // 'cpsr' and 'apsr' are special flags registers, but cannot be referred to directly + arm_with_number_register_list = map{ + 'r#': 16 + } +) + +const ( + riscv_no_number_register_list = ['zero', 'ra', 'sp', 'gp', 'tp'] + riscv_with_number_register_list = map{ + 'x#': 32 + 't#': 3 + 's#': 12 + 'a#': 8 + } +) + pub struct AssertStmt { pub: pos token.Position @@ -1383,7 +1553,17 @@ pub fn (node Node) position() token.Position { } ScopeObject { match node { - ConstField, GlobalField, Var { return node.pos } + ConstField, GlobalField, Var { + return node.pos + } + AsmRegister { + return token.Position{ + len: -1 + line_nr: -1 + pos: -1 + last_line: -1 + } + } } } File { @@ -1510,6 +1690,7 @@ pub fn (node Node) children() []Node { } else if node is ScopeObject { match node { GlobalField, ConstField, Var { children << node.expr } + AsmRegister {} } } else { match node { @@ -1559,3 +1740,93 @@ pub fn (mut lx IndexExpr) recursive_mapset_is_setter(val bool) { } } } + +// return all the registers for a give architecture +pub fn all_registers(mut t table.Table, arch pref.Arch) map[string]ScopeObject { + mut res := map[string]ScopeObject{} + match arch { + .amd64, .i386 { + for bit_size, array in ast.x86_no_number_register_list { + for name in array { + res[name] = AsmRegister{ + name: name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + } + for bit_size, array in ast.x86_with_number_register_list { + for name, max_num in array { + for i in 0 .. max_num { + hash_index := name.index('#') or { + panic('ast.all_registers: no hashtag found') + } + assembled_name := '${name[..hash_index]}$i${name[hash_index + 1..]}' + res[assembled_name] = AsmRegister{ + name: assembled_name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + } + } + } + .aarch32 { + aarch32 := gen_all_registers(mut t, ast.arm_no_number_register_list, ast.arm_with_number_register_list, + 32) + for k, v in aarch32 { + res[k] = v + } + } + .aarch64 { + aarch64 := gen_all_registers(mut t, ast.arm_no_number_register_list, ast.arm_with_number_register_list, + 64) + for k, v in aarch64 { + res[k] = v + } + } + .rv32 { + rv32 := gen_all_registers(mut t, ast.riscv_no_number_register_list, ast.riscv_with_number_register_list, + 32) + for k, v in rv32 { + res[k] = v + } + } + .rv64 { + rv64 := gen_all_registers(mut t, ast.riscv_no_number_register_list, ast.riscv_with_number_register_list, + 64) + for k, v in rv64 { + res[k] = v + } + } + else { // TODO + panic('ast.all_registers: unhandled arch') + } + } + + return res +} + +// only for arm and riscv because x86 has different sized registers +fn gen_all_registers(mut t table.Table, without_numbers []string, with_numbers map[string]int, bit_size int) map[string]ScopeObject { + mut res := map[string]ScopeObject{} + for name in without_numbers { + res[name] = AsmRegister{ + name: name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + for name, max_num in with_numbers { + for i in 0 .. max_num { + hash_index := name.index('#') or { panic('ast.all_registers: no hashtag found') } + assembled_name := '${name[..hash_index]}$i${name[hash_index + 1..]}' + res[assembled_name] = AsmRegister{ + name: assembled_name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + } + return res +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 5df8ed0bd1..bb3596a97a 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1907,7 +1907,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { // builtin C.m*, C.s* only - temp c.warn('function `$f.name` must be called from an `unsafe` block', call_expr.pos) } - if f.mod != 'builtin' && f.language == .v && f.no_body && !c.pref.translated { + if f.mod != 'builtin' && f.language == .v && f.no_body && !c.pref.translated && !f.is_unsafe { c.error('cannot call a function that does not have a body', call_expr.pos) } for generic_type in call_expr.generic_types { @@ -3238,6 +3238,9 @@ fn (mut c Checker) stmt(node ast.Stmt) { } // c.expected_type = table.void_type match mut node { + ast.AsmStmt { + c.asm_stmt(mut node) + } ast.AssertStmt { c.assert_stmt(node) } @@ -3549,6 +3552,113 @@ fn (mut c Checker) go_stmt(mut node ast.GoStmt) { } } +fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) { + if stmt.is_goto { + c.warn('inline assembly goto is not supported, it will most likely not work', + stmt.pos) + } + if c.pref.backend == .js { + c.error('inline assembly is not supported in js backend', stmt.pos) + } + if c.pref.backend == .c && c.pref.ccompiler_type == .msvc { + c.error('msvc compiler does not support inline assembly', stmt.pos) + } + mut aliases := c.asm_ios(stmt.output, mut stmt.scope, true) + aliases2 := c.asm_ios(stmt.input, mut stmt.scope, false) + aliases << aliases2 + for template in stmt.templates { + if template.is_directive { + /* + align n[,value] + .skip n[,value] + .space n[,value] + .byte value1[,...] + .word value1[,...] + .short value1[,...] + .int value1[,...] + .long value1[,...] + .quad immediate_value1[,...] + .globl symbol + .global symbol + .section section + .text + .data + .bss + .fill repeat[,size[,value]] + .org n + .previous + .string string[,...] + .asciz string[,...] + .ascii string[,...] + */ + if template.name !in ['skip', 'space', 'byte', 'word', 'short', 'int', 'long', 'quad', + 'globl', 'global', 'section', 'text', 'data', 'bss', 'fill', 'org', 'previous', + 'string', 'asciz', 'ascii'] { // all tcc supported assembler directive + c.error('unknown assembler directive: `$template.name`', template.pos) + } + // if c.file in { + + // } + } + for arg in template.args { + c.asm_arg(arg, stmt, aliases) + } + } + for clob in stmt.clobbered { + c.asm_arg(clob.reg, stmt, aliases) + } +} + +fn (mut c Checker) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt, aliases []string) { + match mut arg { + ast.AsmAlias { + name := arg.name + if name !in aliases && name !in stmt.local_labels && name !in stmt.global_labels { + suggestion := util.new_suggestion(name, aliases) + c.error(suggestion.say('alias or label `$arg.name` does not exist'), arg.pos) + } + } + ast.AsmAddressing { + if arg.scale !in [-1, 1, 2, 4, 8] { + c.error('scale must be one of 1, 2, 4, or 8', arg.pos) + } + c.asm_arg(arg.base, stmt, aliases) + c.asm_arg(arg.index, stmt, aliases) + } + ast.BoolLiteral {} // all of these are guarented to be correct. + ast.FloatLiteral {} + ast.CharLiteral {} + ast.IntegerLiteral {} + ast.AsmRegister {} // if the register is not found, the parser will register it as an alias + string {} + } +} + +fn (mut c Checker) asm_ios(ios []ast.AsmIO, mut scope ast.Scope, output bool) []string { + mut aliases := []string{} + for io in ios { + typ := c.expr(io.expr) + if output { + c.fail_if_immutable(io.expr) + } + if io.alias != '' { + aliases << io.alias + if io.alias in scope.objects { + scope.objects[io.alias] = ast.Var{ + name: io.alias + expr: io.expr + is_arg: true + typ: typ + orig_type: typ + pos: io.pos + } + } + } + } + + return aliases +} + fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { if c.skip_flags { return @@ -3998,6 +4108,23 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { return table.void_type } +// pub fn (mut c Checker) asm_reg(mut node ast.AsmRegister) table.Type { +// name := node.name + +// for bit_size, array in ast.x86_no_number_register_list { +// if name in array { +// return c.table.bitsize_to_type(bit_size) +// } +// } +// for bit_size, array in ast.x86_with_number_register_list { +// if name in array { +// return c.table.bitsize_to_type(bit_size) +// } +// } +// c.error('invalid register name: `$name`', node.pos) +// return table.void_type +// } + pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) table.Type { node.expr_type = c.expr(node.expr) // type to be casted from_type_sym := c.table.get_type_symbol(node.expr_type) @@ -5286,7 +5413,7 @@ fn (mut c Checker) find_obj_definition(obj ast.ScopeObject) ?ast.Expr { // TODO: remove once we have better type inference mut name := '' match obj { - ast.Var, ast.ConstField, ast.GlobalField { name = obj.name } + ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister { name = obj.name } } mut expr := ast.Expr{} if obj is ast.Var { diff --git a/vlib/v/checker/tests/asm_alias_does_not_exist.out b/vlib/v/checker/tests/asm_alias_does_not_exist.out new file mode 100644 index 0000000000..344c7e0b0d --- /dev/null +++ b/vlib/v/checker/tests/asm_alias_does_not_exist.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/asm_alias_does_not_exist.vv:2:11: error: alias or label `a` does not exist + 1 | asm amd64 { + 2 | mov ebx, a + | ^ + 3 | } diff --git a/vlib/v/checker/tests/asm_alias_does_not_exist.vv b/vlib/v/checker/tests/asm_alias_does_not_exist.vv new file mode 100644 index 0000000000..471862f244 --- /dev/null +++ b/vlib/v/checker/tests/asm_alias_does_not_exist.vv @@ -0,0 +1,3 @@ +asm amd64 { + mov ebx, a +} diff --git a/vlib/v/checker/tests/asm_immutable_err.out b/vlib/v/checker/tests/asm_immutable_err.out new file mode 100644 index 0000000000..2153ae9df2 --- /dev/null +++ b/vlib/v/checker/tests/asm_immutable_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/asm_immutable_err.vv:9:9: error: `c` is immutable, declare it with `mut` to make it mutable + 7 | add eax, b + 8 | mov c, eax + 9 | ; =r (c) // output + | ^ + 10 | ; r (a) // input + 11 | r (b) \ No newline at end of file diff --git a/vlib/v/checker/tests/asm_immutable_err.vv b/vlib/v/checker/tests/asm_immutable_err.vv new file mode 100644 index 0000000000..e991206353 --- /dev/null +++ b/vlib/v/checker/tests/asm_immutable_err.vv @@ -0,0 +1,16 @@ +fn main() { + a := 100 + b := 20 + c := 0 + asm amd64 { + mov eax, a + add eax, b + mov c, eax + ; =r (c) // output + ; r (a) // input + r (b) + } + println('a: $a') // 100 + println('b: $b') // 20 + println('c: $c') // 120 +} diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index bdcb2eafa5..e820e9db7f 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -178,9 +178,11 @@ fn (mut tasks Tasks) run() { m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' } $if msvc { + m_skip_files << 'vlib/v/checker/tests/asm_alias_does_not_exist.vv' + m_skip_files << 'vlib/v/checker/tests/asm_immutable_err.vv' // TODO: investigate why MSVC regressed m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv' - m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' } for i in 0 .. tasks.all.len { if tasks.all[i].path in m_skip_files { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 1dc1b8b5bd..2a87849554 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -382,6 +382,9 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}') } match node { + ast.AsmStmt { + f.asm_stmt(node) + } ast.AssignStmt { f.assign_stmt(node) } @@ -462,6 +465,151 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { } } +fn (mut f Fmt) asm_stmt(stmt ast.AsmStmt) { + f.writeln('asm $stmt.arch {') + f.indent++ + for template in stmt.templates { + if template.is_directive { + f.write('.') + } + f.write('$template.name') + if template.is_label { + f.write(':') + } else { + f.write(' ') + } + for i, arg in template.args { + f.asm_arg(arg) + if i + 1 < template.args.len { + f.write(', ') + } + } + if template.comments.len == 0 { + f.writeln('') + } else { + f.comments(template.comments, inline: false) + } + } + if stmt.output.len != 0 || stmt.input.len != 0 || stmt.clobbered.len != 0 { + f.write('; ') + } + f.asm_ios(stmt.output) + + if stmt.input.len != 0 || stmt.clobbered.len != 0 { + f.write('; ') + } + f.asm_ios(stmt.input) + + if stmt.clobbered.len != 0 { + f.write('; ') + } + for i, clob in stmt.clobbered { + if i != 0 { + f.write(' ') + } + f.write(clob.reg.name) + + if clob.comments.len == 0 { + f.writeln('') + } else { + f.comments(clob.comments, inline: false) + } + } + f.indent-- + f.writeln('}') +} + +fn (mut f Fmt) asm_arg(arg ast.AsmArg) { + match arg { + ast.AsmRegister { + f.asm_reg(arg) + } + ast.AsmAlias { + f.write('$arg.name') + } + ast.IntegerLiteral, ast.FloatLiteral, ast.CharLiteral { + f.write(arg.val) + } + ast.BoolLiteral { + f.write(arg.val.str()) + } + string { + f.write(arg) + } + ast.AsmAddressing { + f.write('[') + base := arg.base + index := arg.index + displacement := arg.displacement + scale := arg.scale + match arg.mode { + .base { + f.asm_arg(base) + } + .displacement { + f.write('$displacement') + } + .base_plus_displacement { + f.asm_arg(base) + f.write(' + $displacement') + } + .index_times_scale_plus_displacement { + f.asm_arg(index) + f.write(' * $scale + $displacement') + } + .base_plus_index_plus_displacement { + f.asm_arg(base) + f.write(' + ') + f.asm_arg(index) + f.write(' + $displacement') + } + .base_plus_index_times_scale_plus_displacement { + f.asm_arg(base) + f.write(' + ') + f.asm_arg(index) + f.write(' * $scale + $displacement') + } + .rip_plus_displacement { + f.asm_arg(base) + f.write(' + $displacement') + } + .invalid { + panic('fmt: invalid addressing mode') + } + } + f.write(']') + } + } +} + +fn (mut f Fmt) asm_reg(reg ast.AsmRegister) { + f.write(reg.name) +} + +fn (mut f Fmt) asm_ios(ios []ast.AsmIO) { + for i, io in ios { + if i != 0 { + f.write(' ') + } + + f.write('$io.constraint ($io.expr)') + mut as_block := true + if io.expr is ast.Ident { + if io.expr.name == io.alias { + as_block = false + } + } + if as_block && io.alias != '' { + f.write(' as $io.alias') + } + if io.comments.len == 0 { + f.writeln('') + } else { + f.comments(io.comments, inline: false) + } + } +} + fn stmt_is_single_line(stmt ast.Stmt) bool { return match stmt { ast.ExprStmt, ast.AssertStmt { expr_is_single_line(stmt.expr) } @@ -734,8 +882,8 @@ pub fn (mut f Fmt) expr(node ast.Expr) { f.write('none') } ast.OrExpr { - // shouldn't happen, an or expression is always linked to a call expr - panic('fmt: OrExpr should be linked to CallExpr') + // shouldn't happen, an or expression is always linked to a call expr or index expr + panic('fmt: OrExpr should be linked to ast.CallExpr or ast.IndexExpr') } ast.ParExpr { f.par_expr(node) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c87e471b81..8a3bdede9e 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -19,7 +19,9 @@ const ( 'delete', 'do', 'double', 'else', 'enum', 'error', 'exit', 'export', 'extern', 'float', 'for', 'free', 'goto', 'if', 'inline', 'int', 'link', 'long', 'malloc', 'namespace', 'new', 'panic', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', - 'switch', 'typedef', 'typename', 'union', 'unix', 'unsigned', 'void', 'volatile', 'while'] + 'switch', 'typedef', 'typename', 'union', 'unix', 'unsigned', 'void', 'volatile', 'while', + 'template', + ] // same order as in token.Kind cmp_str = ['eq', 'ne', 'gt', 'lt', 'ge', 'le'] // when operands are switched @@ -1001,6 +1003,10 @@ fn (mut g Gen) stmt(node ast.Stmt) { // println('g.stmt()') // g.writeln('//// stmt start') match node { + ast.AsmStmt { + g.write_v_source_line_info(node.pos) + g.gen_asm_stmt(node) + } ast.AssertStmt { g.write_v_source_line_info(node.pos) g.gen_assert_stmt(node) @@ -1740,6 +1746,177 @@ fn (mut g Gen) gen_attrs(attrs []table.Attr) { } } +fn (mut g Gen) gen_asm_stmt(stmt ast.AsmStmt) { + g.write('__asm__') + if stmt.is_volatile { + g.write(' volatile') + } + if stmt.is_goto { + g.write(' goto') + } + g.writeln(' (') + g.indent++ + for mut template in stmt.templates { + g.write('"') + if template.is_directive { + g.write('.') + } + g.write(template.name) + if template.is_label { + g.write(':') + } else { + g.write(' ') + } + // swap destionation and operands for att syntax + if template.args.len != 0 { + template.args.prepend(template.args[template.args.len - 1]) + template.args.delete(template.args.len - 1) + } + + for i, arg in template.args { + g.asm_arg(arg, stmt) + if i + 1 < template.args.len { + g.write(', ') + } + } + + if !template.is_label { + g.write(';') + } + g.writeln('"') + } + if !stmt.is_top_level { + g.write(': ') + } + g.gen_asm_ios(stmt.output) + if stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + g.gen_asm_ios(stmt.input) + if stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + for i, clob in stmt.clobbered { + g.write('"') + g.write(clob.reg.name) + g.write('"') + if i + 1 < stmt.clobbered.len { + g.writeln(',') + } else { + g.writeln('') + } + } + if stmt.is_goto { + g.write(': ') + } + for i, label in stmt.global_labels { + g.write(label) + if i + 1 < stmt.clobbered.len { + g.writeln(',') + } else { + g.writeln('') + } + } + g.indent-- + g.writeln(');') +} + +fn (mut g Gen) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt) { + match arg { + ast.AsmAlias { + name := arg.name + if name in stmt.local_labels || name in stmt.global_labels { + g.write(if name in stmt.local_labels { + name + } else { // val in stmt.global_labels + '%l[$name]' + }) + } else { + g.write('%[$name]') + } + } + ast.CharLiteral { + g.write("'$arg.val'") + } + ast.IntegerLiteral, ast.FloatLiteral { + g.write('\$$arg.val') + } + ast.BoolLiteral { + g.write('\$$arg.val.str()') + } + ast.AsmRegister { + if !stmt.is_top_level { + g.write('%') // escape percent in extended assembly + } + g.write('%$arg.name') + } + ast.AsmAddressing { + base := arg.base + index := arg.index + displacement := arg.displacement + scale := arg.scale + match arg.mode { + .base { + g.write('(') + g.asm_arg(base, stmt) + } + .displacement { + g.write('${displacement}(') + } + .base_plus_displacement { + g.write('${displacement}(') + g.asm_arg(base, stmt) + } + .index_times_scale_plus_displacement { + g.write('${displacement}(,') + g.asm_arg(index, stmt) + g.write(',') + g.write(scale.str()) + } + .base_plus_index_plus_displacement { + g.write('${displacement}(') + g.asm_arg(base, stmt) + g.write(',') + g.asm_arg(index, stmt) + g.write(',1') + } + .base_plus_index_times_scale_plus_displacement { + g.write('${displacement}(') + g.asm_arg(base, stmt) + g.write(',') + g.asm_arg(index, stmt) + g.write(',$scale') + } + .rip_plus_displacement { + g.write('${displacement}(') + g.asm_arg(base, stmt) + } + .invalid { + g.error('invalid addressing mode', arg.pos) + } + } + g.write(')') + } + string { + g.write('$arg') + } + } +} + +fn (mut g Gen) gen_asm_ios(ios []ast.AsmIO) { + for i, io in ios { + if io.alias != '' { + g.write('[$io.alias] ') + } + g.write('"$io.constraint" ($io.expr)') + if i + 1 < ios.len { + g.writeln(',') + } else { + g.writeln('') + } + } +} + fn cnewlines(s string) string { return s.replace('\n', r'\n') } @@ -3116,6 +3293,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { return } left_type := g.unwrap_generic(node.left_type) + // println('>>$node') left_sym := g.table.get_type_symbol(left_type) left_final_sym := g.table.get_final_type_symbol(left_type) unaliased_left := if left_sym.kind == .alias { diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 76575975fa..a1c90c4a50 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -356,6 +356,9 @@ fn (mut g JsGen) stmts(stmts []ast.Stmt) { fn (mut g JsGen) stmt(node ast.Stmt) { g.stmt_start_pos = g.ns.out.len match node { + ast.AsmStmt { + panic('inline asm is not supported by js') + } ast.AssertStmt { g.gen_assert_stmt(node) } diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index cd6f71aec7..023af6d313 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -49,6 +49,10 @@ pub fn (mut w Walker) mark_root_fns(all_fn_root_names []string) { pub fn (mut w Walker) stmt(node ast.Stmt) { match mut node { + ast.AsmStmt { + w.asm_io(node.output) + w.asm_io(node.input) + } ast.AssertStmt { w.expr(node.expr) w.n_asserts++ @@ -122,6 +126,12 @@ pub fn (mut w Walker) stmt(node ast.Stmt) { } } +fn (mut w Walker) asm_io(ios []ast.AsmIO) { + for io in ios { + w.expr(io.expr) + } +} + fn (mut w Walker) defer_stmts(stmts []ast.DeferStmt) { for stmt in stmts { w.stmts(stmt.stmts) @@ -145,15 +155,15 @@ fn (mut w Walker) expr(node ast.Expr) { ast.AnonFn { w.fn_decl(mut node.decl) } - ast.Assoc { - w.exprs(node.exprs) - } ast.ArrayInit { w.expr(node.len_expr) w.expr(node.cap_expr) w.expr(node.default_expr) w.exprs(node.exprs) } + ast.Assoc { + w.exprs(node.exprs) + } ast.ArrayDecompose { w.expr(node.expr) } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index da228f8c9b..4ee933a460 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -23,52 +23,55 @@ mut: file_base string // "hello.v" file_name string // "/home/user/hello.v" file_name_dir string // "/home/user" - file_backend_mode table.Language // .c for .c.v|.c.vv|.c.vsh files; .js for .js.v files, .v otherwise. + file_backend_mode table.Language // .c for .c.v|.c.vv|.c.vsh files; .js for .js.v files, .amd64/.rv32/other arches for .amd64.v/.rv32.v/etc. files, .v otherwise. scanner &scanner.Scanner comments_mode scanner.CommentsMode = .skip_comments // see comment in parse_file - tok token.Token - prev_tok token.Token - peek_tok token.Token - table &table.Table - language table.Language - inside_if bool - inside_if_expr bool - inside_ct_if_expr bool - inside_or_expr bool - inside_for bool - inside_fn bool // true even with implicit main - inside_unsafe_fn bool // true when in fn, marked with `[unsafe]` - inside_str_interp bool - or_is_handled bool // ignore `or` in this expression - builtin_mod bool // are we in the `builtin` module? - mod string // current module name - is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree - attrs []table.Attr // attributes before next decl stmt - expr_mod string // for constructing full type names in parse_type() - scope &ast.Scope - global_scope &ast.Scope - imports map[string]string // alias => mod_name - ast_imports []ast.Import // mod_names - used_imports []string // alias - auto_imports []string // imports, the user does not need to specify - imported_symbols map[string]string - is_amp bool // for generating the right code for `&Foo{}` - returns bool - inside_match bool // to separate `match A { }` from `Struct{}` - inside_select bool // to allow `ch <- Struct{} {` inside `select` - inside_match_case bool // to separate `match_expr { }` from `Struct{}` - inside_match_body bool // to fix eval not used TODO - inside_unsafe bool - is_stmt_ident bool // true while the beginning of a statement is an ident/selector - expecting_type bool // `is Type`, expecting type - errors []errors.Error - warnings []errors.Warning - vet_errors []vet.Error - cur_fn_name string - label_names []string - in_generic_params bool // indicates if parsing between `<` and `>` of a method/function - name_error bool // indicates if the token is not a name or the name is on another line + tok token.Token + prev_tok token.Token + peek_tok token.Token + table &table.Table + language table.Language + inside_if bool + inside_if_expr bool + inside_ct_if_expr bool + inside_or_expr bool + inside_for bool + inside_fn bool // true even with implicit main + inside_unsafe_fn bool + inside_str_interp bool + or_is_handled bool // ignore `or` in this expression + builtin_mod bool // are we in the `builtin` module? + mod string // current module name + is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree + attrs []table.Attr // attributes before next decl stmt + expr_mod string // for constructing full type names in parse_type() + scope &ast.Scope + global_scope &ast.Scope + imports map[string]string // alias => mod_name + ast_imports []ast.Import // mod_names + used_imports []string // alias + auto_imports []string // imports, the user does not need to specify + imported_symbols map[string]string + is_amp bool // for generating the right code for `&Foo{}` + returns bool + inside_match bool // to separate `match A { }` from `Struct{}` + inside_select bool // to allow `ch <- Struct{} {` inside `select` + inside_match_case bool // to separate `match_expr { }` from `Struct{}` + inside_match_body bool // to fix eval not used TODO + inside_unsafe bool + is_stmt_ident bool // true while the beginning of a statement is an ident/selector + expecting_type bool // `is Type`, expecting type + errors []errors.Error + warnings []errors.Warning + vet_errors []vet.Error + cur_fn_name string + label_names []string + in_generic_params bool // indicates if parsing between `<` and `>` of a method/function + name_error bool // indicates if the token is not a name or the name is on another line + n_asm int // controls assembly labels + inside_asm_template bool + inside_asm bool } // for tests @@ -134,14 +137,28 @@ pub fn (mut p Parser) set_path(path string) { p.file_name = path p.file_base = os.base(path) p.file_name_dir = os.dir(path) - if path.ends_with('_c.v') || path.ends_with('.c.v') || path.ends_with('.c.vv') - || path.ends_with('.c.vsh') { - p.file_backend_mode = .c - } else if path.ends_with('_js.v') || path.ends_with('.js.v') || path.ends_with('.js.vv') - || path.ends_with('.js.vsh') { - p.file_backend_mode = .js - } else { + before_dot_v := path.before('.v') // also works for .vv and .vsh + language := before_dot_v.all_after_last('.') + langauge_with_underscore := before_dot_v.all_after_last('_') + if language == before_dot_v && langauge_with_underscore == before_dot_v { p.file_backend_mode = .v + return + } + actual_language := if language == before_dot_v { langauge_with_underscore } else { language } + match actual_language { + 'c' { + p.file_backend_mode = .c + } + 'js' { + p.file_backend_mode = .js + } + else { + arch := pref.arch_from_string(actual_language) or { pref.Arch._auto } + p.file_backend_mode = table.pref_arch_to_table_language(arch) + if arch == ._auto { + p.file_backend_mode = .v + } + } } } @@ -345,7 +362,7 @@ pub fn (mut p Parser) init_parse_fns() { } pub fn (mut p Parser) read_first_token() { - // need to call next() 4 times to get peek token 1,2,3 and current token + // need to call next() 2 times to get peek token and current token p.next() p.next() } @@ -430,7 +447,8 @@ fn (mut p Parser) check(expected token.Kind) { // for p.tok.kind in [.line_comment, .mline_comment] { // p.next() // } - if p.tok.kind == expected { + + if _likely_(p.tok.kind == expected) { p.next() } else { if expected == .name { @@ -507,6 +525,9 @@ pub fn (mut p Parser) top_stmt() ast.Stmt { p.attributes() continue } + .key_asm { + return p.asm_stmt(true) + } .key_interface { return p.interface_decl() } @@ -802,6 +823,9 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { p.tok.position()) return ast.Stmt{} } + .key_asm { + return p.asm_stmt(false) + } // literals, 'if', etc. in here else { return p.parse_multi_expr(is_top_level) @@ -809,6 +833,523 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } } +fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { + p.inside_asm = true + p.inside_asm_template = true + defer { + p.inside_asm = false + p.inside_asm_template = false + } + p.n_asm = 0 + if is_top_level { + p.top_level_statement_start() + } + mut backup_scope := p.scope + + pos := p.tok.position() + + p.check(.key_asm) + mut arch := pref.arch_from_string(p.tok.lit) or { pref.Arch._auto } + mut is_volatile := false + mut is_goto := false + if p.tok.lit == 'volatile' && p.tok.kind == .name { + arch = pref.arch_from_string(p.peek_tok.lit) or { pref.Arch._auto } + is_volatile = true + p.check(.name) + } else if p.tok.kind == .key_goto { + arch = pref.arch_from_string(p.peek_tok.lit) or { pref.Arch._auto } + is_goto = true + p.check(.key_goto) + } + if arch == ._auto && !p.pref.is_fmt { + p.error('unknown assembly architecture') + } + if p.tok.kind != .name { + p.error('must specify assembly architecture') + } else { + p.check(.name) + } + + p.check_for_impure_v(table.pref_arch_to_table_language(arch), p.prev_tok.position()) + + p.check(.lcbr) + p.scope = &ast.Scope{ + parent: 0 // you shouldn't be able to reference other variables in assembly blocks + detached_from_parent: true + start_pos: p.tok.pos + objects: ast.all_registers(mut p.table, arch) // + } + + mut local_labels := []string{} + mut exported_symbols := []string{} + // riscv: https://github.com/jameslzhu/riscv-card/blob/master/riscv-card.pdf + // x86: https://www.felixcloutier.com/x86/ + // arm: https://developer.arm.com/documentation/dui0068/b/arm-instruction-reference + mut templates := []ast.AsmTemplate{} + for p.tok.kind !in [.semicolon, .rcbr] { + template_pos := p.tok.position() + mut name := '' + is_directive := p.tok.kind == .dot + if is_directive { + p.check(.dot) + } + if p.tok.kind in [.key_in, .key_lock, .key_orelse] { // `in`, `lock`, `or` are v keywords that are also x86/arm/riscv instructions. + name = p.tok.kind.str() + p.next() + } else { + name = p.tok.lit + p.check(.name) + } + // dots are part of instructions for some riscv extensions + if arch in [.rv32, .rv64] { + for p.tok.kind == .dot { + name += '.' + p.check(.dot) + name += p.tok.lit + p.check(.name) + } + } + mut is_label := false + + mut args := []ast.AsmArg{} + args_loop: for { + match p.tok.kind { + .name { + args << p.reg_or_alias() + } + .number { + number_lit := p.parse_number_literal() + match number_lit { + ast.FloatLiteral { + args << ast.FloatLiteral{ + ...number_lit + } + } + ast.IntegerLiteral { + args << ast.IntegerLiteral{ + ...number_lit + } + } + else { + verror('p.parse_number_literal() invalid output: `$number_lit`') + } + } + } + .chartoken { + args << ast.CharLiteral{ + val: p.tok.lit + pos: p.tok.position() + } + p.check(.chartoken) + } + .colon { + is_label = true + p.check(.colon) + local_labels << name + break + } + .lsbr { + args << p.asm_addressing() + } + .rcbr { + break + } + .semicolon { + break + } + else { + p.error('invalid token in assembly block') + } + } + if p.tok.kind == .comma { + p.check(.comma) + } else { + break + } + } + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + } + if is_directive && name in ['globl', 'global'] { + exported_symbols << args + } + templates << ast.AsmTemplate{ + name: name + args: args + comments: comments + is_label: is_label + is_directive: is_directive + pos: template_pos.extend(p.tok.position()) + } + } + mut scope := p.scope + p.scope = backup_scope + p.inside_asm_template = false + mut output, mut input, mut clobbered, mut global_labels := []ast.AsmIO{}, []ast.AsmIO{}, []ast.AsmClobbered{}, []string{} + if !is_top_level { + if p.tok.kind == .semicolon { + output = p.asm_ios(true) + if p.tok.kind == .semicolon { + input = p.asm_ios(false) + } + if p.tok.kind == .semicolon { + // because p.reg_or_alias() requires the scope with registers to recognize registers. + backup_scope = p.scope + p.scope = scope + p.check(.semicolon) + for p.tok.kind == .name { + reg := p.reg_or_alias() + + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + } + if reg is ast.AsmRegister { + clobbered << ast.AsmClobbered{ + reg: reg + comments: comments + } + } else { + p.error('not a register: $reg') + } + if p.tok.kind in [.rcbr, .semicolon] { + break + } + } + + if is_goto && p.tok.kind == .semicolon { + p.check(.semicolon) + for p.tok.kind == .name { + global_labels << p.tok.lit + p.check(.name) + } + } + } + } + } else if p.tok.kind == .semicolon { + p.error('extended assembly is not allowed as a top level statement') + } + p.scope = backup_scope + p.check(.rcbr) + if is_top_level { + p.top_level_statement_end() + } + scope.end_pos = p.prev_tok.pos + + return ast.AsmStmt{ + arch: arch + is_goto: is_goto + is_volatile: is_volatile + templates: templates + output: output + input: input + clobbered: clobbered + pos: pos.extend(p.tok.position()) + is_top_level: is_top_level + scope: scope + global_labels: global_labels + local_labels: local_labels + exported_symbols: exported_symbols + } +} + +fn (mut p Parser) reg_or_alias() ast.AsmArg { + assert p.tok.kind == .name + if p.tok.lit in p.scope.objects { + x := p.scope.objects[p.tok.lit] + if x is ast.AsmRegister { + b := x + p.check(.name) + return b + } else { + panic('parser bug: non-register ast.ScopeObject found in scope') + } + } else { + p.check(.name) + return ast.AsmAlias{ + name: p.prev_tok.lit + pos: p.prev_tok.position() + } + } +} + +// fn (mut p Parser) asm_addressing() ast.AsmAddressing { +// pos := p.tok.position() +// p.check(.lsbr) +// unknown_addressing_mode := 'unknown addressing mode. supported ones are [displacement], [base], [base + displacement] [index ∗ scale + displacement], [base + index ∗ scale + displacement], [base + index + displacement] [rip + displacement]' +// mut mode := ast.AddressingMode.invalid +// if p.peek_tok.kind == .rsbr { +// if p.tok.kind == .name { +// mode = .base +// } else if p.tok.kind == .number { +// mode = .displacement +// } else { +// p.error(unknown_addressing_mode) +// } +// } else if p.peek_tok.kind == .mul { +// mode = .index_times_scale_plus_displacement +// } else if p.tok.lit == 'rip' { +// mode = .rip_plus_displacement +// } else if p.peek_tok3.kind == .mul { +// mode = .base_plus_index_times_scale_plus_displacement +// } else if p.peek_tok.kind == .plus && p.peek_tok3.kind == .rsbr { +// mode = .base_plus_displacement +// } else if p.peek_tok.kind == .plus && p.peek_tok3.kind == .plus { +// mode = .base_plus_index_plus_displacement +// } else { +// p.error(unknown_addressing_mode) +// } +// mut displacement, mut base, mut index, mut scale := u32(0), ast.AsmArg{}, ast.AsmArg{}, -1 + +// match mode { +// .base { +// base = p.reg_or_alias() +// } +// .displacement { +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .base_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .index_times_scale_plus_displacement { +// index = p.reg_or_alias() +// p.check(.mul) +// scale = p.tok.lit.int() +// p.check(.number) +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .base_plus_index_times_scale_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// index = p.reg_or_alias() +// p.check(.mul) +// scale = p.tok.lit.int() +// p.check(.number) +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .rip_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .base_plus_index_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// index = p.reg_or_alias() +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .invalid {} // there was already an error above +// } + +// p.check(.rsbr) +// return ast.AsmAddressing{ +// base: base +// displacement: displacement +// index: index +// scale: scale +// mode: mode +// pos: pos.extend(p.prev_tok.position()) +// } +// } +fn (mut p Parser) asm_addressing() ast.AsmAddressing { + pos := p.tok.position() + p.check(.lsbr) + unknown_addressing_mode := 'unknown addressing mode. supported ones are [displacement], [base], [base + displacement], [index ∗ scale + displacement], [base + index ∗ scale + displacement], [base + index + displacement], [rip + displacement]' + // this mess used to look much cleaner before the removal of peek_tok3, see above + if p.peek_tok.kind == .rsbr { // [displacement] or [base] + if p.tok.kind == .name { + base := p.reg_or_alias() + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base + base: base + pos: pos.extend(p.prev_tok.position()) + } + } else if p.tok.kind == .number { + displacement := p.tok.lit.u32() + p.check(.name) + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .displacement + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } else { + p.error(unknown_addressing_mode) + } + } + if p.peek_tok.kind == .plus && p.tok.kind == .name { // [base + displacement], [base + index ∗ scale + displacement], [base + index + displacement] or [rip + displacement] + if p.tok.lit == 'rip' { + p.check(.name) + p.check(.plus) + displacement := p.tok.lit.u32() + p.check(.number) + return ast.AsmAddressing{ + mode: .rip_plus_displacement + base: 'rip' + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } + base := p.reg_or_alias() + p.check(.plus) + if p.peek_tok.kind == .rsbr { + if p.tok.kind == .number { + displacement := p.tok.lit.u32() + p.check(.number) + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base_plus_displacement + base: base + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } else { + p.error(unknown_addressing_mode) + } + } + index := p.reg_or_alias() + if p.tok.kind == .mul { + p.check(.mul) + scale := p.tok.lit.int() + p.check(.number) + p.check(.plus) + displacement := p.tok.lit.u32() + p.check(.number) + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base_plus_index_times_scale_plus_displacement + base: base + index: index + scale: scale + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } else if p.tok.kind == .plus { + p.check(.plus) + displacement := p.tok.lit.u32() + p.check(.number) + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base_plus_index_plus_displacement + base: base + index: index + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } + } + if p.peek_tok.kind == .mul { // [index ∗ scale + displacement] + index := p.reg_or_alias() + p.check(.mul) + scale := p.tok.lit.int() + p.check(.number) + p.check(.plus) + displacement := p.tok.lit.u32() + p.check(.number) + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .index_times_scale_plus_displacement + index: index + scale: scale + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } + p.error(unknown_addressing_mode) + return ast.AsmAddressing{} +} + +fn (mut p Parser) asm_ios(output bool) []ast.AsmIO { + mut res := []ast.AsmIO{} + p.check(.semicolon) + if p.tok.kind in [.rcbr, .semicolon] { + return [] + } + for { + pos := p.tok.position() + + mut constraint := '' + if p.tok.kind == .lpar { + constraint = if output { '+r' } else { 'r' } // default constraint + } else { + constraint += match p.tok.kind { + .assign { + '=' + } + .plus { + '+' + } + .mod { + '%' + } + .amp { + '&' + } + else { + '' + } + } + if constraint != '' { + p.next() + } + if p.tok.kind == .assign { + constraint += '=' + p.check(.assign) + } else if p.tok.kind == .plus { + constraint += '+' + p.check(.plus) + } + constraint += p.tok.lit + p.check(.name) + } + mut expr := p.expr(0) + if mut expr is ast.ParExpr { + expr = expr.expr + } else { + p.error('asm in/output must be incolsed in brackets $expr.type_name()') + } + mut alias := '' + if p.tok.kind == .key_as { + p.check(.key_as) + alias = p.tok.lit + p.check(.name) + } else if mut expr is ast.Ident { + alias = expr.name + } + // for constraints like `a`, no alias is needed, it is reffered to as rcx + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + } + + res << ast.AsmIO{ + alias: alias + constraint: constraint + expr: expr + comments: comments + pos: pos.extend(p.prev_tok.position()) + } + p.n_asm++ + if p.tok.kind in [.semicolon, .rcbr] { + break + } + } + return res +} + fn (mut p Parser) expr_list() ([]ast.Expr, []ast.Comment) { mut exprs := []ast.Expr{} mut comments := []ast.Comment{} diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index c030bca216..64b08b774e 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -322,7 +322,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { } } else { - if p.tok.kind != .eof { + if p.tok.kind != .eof && !(p.tok.kind == .rsbr && p.inside_asm) { // eof should be handled where it happens p.error_with_pos('invalid expression: unexpected $p.tok', p.tok.position()) return ast.Expr{} @@ -360,13 +360,17 @@ pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_iden } } else if p.tok.kind == .key_as { // sum type as cast `x := SumType as Variant` - pos := p.tok.position() - p.next() - typ := p.parse_type() - node = ast.AsCast{ - expr: node - typ: typ - pos: pos + if !p.inside_asm { + pos := p.tok.position() + p.next() + typ := p.parse_type() + node = ast.AsCast{ + expr: node + typ: typ + pos: pos + } + } else { + return node } } else if p.tok.kind == .left_shift && p.is_stmt_ident { // arr << elem diff --git a/vlib/v/parser/v_parser_test.v b/vlib/v/parser/v_parser_test.v index 06d496b0bd..72ffc93eb7 100644 --- a/vlib/v/parser/v_parser_test.v +++ b/vlib/v/parser/v_parser_test.v @@ -6,6 +6,7 @@ import v.gen.c import v.table import v.checker import v.pref +import v.scanner import term fn test_eval() { diff --git a/vlib/v/pref/default.v b/vlib/v/pref/default.v index 075ff1e3d1..d86fc02e29 100644 --- a/vlib/v/pref/default.v +++ b/vlib/v/pref/default.v @@ -71,6 +71,7 @@ pub fn (mut p Preferences) fill_with_defaults() { p.find_cc_if_cross_compiling() p.ccompiler_type = cc_from_string(p.ccompiler) p.is_test = p.path.ends_with('_test.v') || p.path.ends_with('_test.vv') + || p.path.all_before_last('.v').all_before_last('.').ends_with('_test') p.is_vsh = p.path.ends_with('.vsh') p.is_script = p.is_vsh || p.path.ends_with('.v') || p.path.ends_with('.vv') if p.third_party_option == '' { diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 666eb21aee..e050244d15 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -43,9 +43,19 @@ pub enum CompilerType { cplusplus } +pub enum Arch { + _auto + amd64 // aka x86_64 + aarch64 // 64-bit arm + aarch32 // 32-bit arm + rv64 // 64-bit risc-v + rv32 // 32-bit risc-v + i386 +} + const ( list_of_flags_with_param = ['o', 'd', 'define', 'b', 'backend', 'cc', 'os', 'target-os', 'cf', - 'cflags', 'path'] + 'cflags', 'path', 'arch'] ) [heap] @@ -54,6 +64,7 @@ pub mut: os OS // the OS to compile for backend Backend build_mode BuildMode + arch Arch output_mode OutputMode = .stdout // verbosity VerboseLevel is_verbose bool @@ -162,6 +173,16 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences res.is_apk = true res.build_options << arg } + '-arch' { + target_arch := cmdline.option(current_args, '-arch', '') + i++ + target_arch_kind := arch_from_string(target_arch) or { + eprintln('unknown architercture target `$target_arch`') + exit(1) + } + res.arch = target_arch_kind + res.build_options << '$arg $target_arch' + } '-show-timings' { res.show_timings = true } @@ -524,6 +545,41 @@ pub fn (pref &Preferences) vrun_elog(s string) { } } +pub fn arch_from_string(arch_str string) ?Arch { + match arch_str { + 'amd64', 'x86_64', 'x64', 'x86' { // amd64 recommended + + return Arch.amd64 + } + 'aarch64', 'arm64' { // aarch64 recommended + + return Arch.aarch64 + } + 'arm32', 'aarch32', 'arm' { // aarch32 recommended + + return Arch.aarch32 + } + 'rv64', 'riscv64', 'risc-v64', 'riscv', 'risc-v' { // rv64 recommended + + return Arch.rv64 + } + 'rv32', 'riscv32' { // rv32 recommended + + return Arch.rv32 + } + 'x86_32', 'x32', 'i386', 'IA-32', 'ia-32', 'ia32' { // i386 recommended + + return Arch.i386 + } + '' { + return ._auto + } + else { + return error('invalid arch: $arch_str') + } + } +} + fn must_exist(path string) { if !os.exists(path) { eprintln('v expects that `$path` exists, but it does not') @@ -576,6 +632,22 @@ pub fn cc_from_string(cc_str string) CompilerType { return .gcc } +pub fn get_host_arch() Arch { + $if amd64 { + return .amd64 + } + // $if i386 { + // return .amd64 + // } + $if aarch64 { + return .aarch64 + } + // $if aarch32 { + // return .aarch32 + // } + panic('unknown host OS') +} + fn parse_define(mut prefs Preferences, define string) { define_parts := define.split('=') prefs.build_options << '-d $define' diff --git a/vlib/v/pref/should_compile.v b/vlib/v/pref/should_compile.v index f20917d6dd..009667d87c 100644 --- a/vlib/v/pref/should_compile.v +++ b/vlib/v/pref/should_compile.v @@ -11,7 +11,8 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s if !file.ends_with('.v') && !file.ends_with('.vh') { continue } - if file.ends_with('_test.v') { + if file.ends_with('_test.v') + || file.all_before_last('.v').all_before_last('.').ends_with('_test') { continue } if prefs.backend == .c && !prefs.should_compile_c(file) { @@ -20,6 +21,9 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s if prefs.backend == .js && !prefs.should_compile_js(file) { continue } + if prefs.backend != .js && !prefs.should_compile_asm(file) { + continue + } if file.contains('_d_') { if prefs.compile_defines_all.len == 0 { continue @@ -99,7 +103,7 @@ fn fname_without_platform_postfix(file string) string { } pub fn (prefs &Preferences) should_compile_c(file string) bool { - if !file.ends_with('.c.v') && file.split('.').len > 2 { + if file.ends_with('.js.v') { // Probably something like `a.js.v`. return false } @@ -142,6 +146,24 @@ pub fn (prefs &Preferences) should_compile_c(file string) bool { return true } +pub fn (prefs &Preferences) should_compile_asm(path string) bool { + if path.count('.') != 2 || path.ends_with('c.v') || path.ends_with('js.v') { + return true + } + file := path.all_before_last('.v') + arch := arch_from_string(file.all_after_last('.')) or { Arch._auto } + + if arch != prefs.arch && prefs.arch != ._auto && arch != ._auto { + return false + } + os := os_from_string(file.all_after_last('_').all_before('.')) or { OS._auto } + + if os != prefs.os && prefs.os != ._auto && os != ._auto { + return false + } + return true +} + pub fn (prefs &Preferences) should_compile_js(file string) bool { if !file.ends_with('.js.v') && file.split('.').len > 2 { // Probably something like `a.c.v`. diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index ed73e1889f..6a0c6ad587 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -903,3 +903,38 @@ pub fn (mytable &Table) has_deep_child_no_ref(ts &TypeSymbol, name string) bool } return false } + +// bitsize_to_type returns a type corresponding to the bit_size +// Examples: +// +// `8 > i8` +// +// `32 > int` +// +// `123 > panic()` +// +// `128 > [16]byte` +// +// `608 > [76]byte` +pub fn (mut t Table) bitsize_to_type(bit_size int) Type { + match bit_size { + 8 { + return i8_type + } + 16 { + return i16_type + } + 32 { + return int_type + } + 64 { + return i64_type + } + else { + if bit_size % 8 != 0 { // there is no way to do `i2131(32)` so this should never be reached + panic('compiler bug: bitsizes must be multiples of 8') + } + return new_type(t.find_or_register_array_fixed(byte_type, bit_size / 8)) + } + } +} diff --git a/vlib/v/table/types.v b/vlib/v/table/types.v index 6055926e51..c3200fb001 100644 --- a/vlib/v/table/types.v +++ b/vlib/v/table/types.v @@ -12,6 +12,7 @@ module table import strings +import v.pref pub type Type = int @@ -22,6 +23,38 @@ pub enum Language { v c js + amd64 // aka x86_64 + i386 + aarch64 // 64-bit arm + aarch32 // 32-bit arm + rv64 // 64-bit risc-v + rv32 // 32-bit risc-v +} + +pub fn pref_arch_to_table_language(pref_arch pref.Arch) Language { + return match pref_arch { + .amd64 { + Language.amd64 + } + .aarch64 { + Language.aarch64 + } + .aarch32 { + Language.aarch32 + } + .rv64 { + Language.rv64 + } + .rv32 { + Language.rv32 + } + .i386 { + Language.i386 + } + ._auto { + Language.v + } + } } // Represents a type that only needs an identifier, e.g. int, array_int. diff --git a/vlib/v/tests/asm_test.v b/vlib/v/tests/asm_test.v deleted file mode 100644 index 89e9d9decb..0000000000 --- a/vlib/v/tests/asm_test.v +++ /dev/null @@ -1,29 +0,0 @@ -fn test_inline_asm() { - /* - // QTODO - a := 10 - b := 0 - unsafe { - asm { - "movl %1, %%eax;" - "movl %%eax, %0;" - :"=r"(b) - :"r"(a) - :"%eax" - } - } - assert a == 10 - assert b == 10 - // - e := 0 - unsafe { - asm { - //".intel_syntax noprefix;" - //"mov %0, 5" - "movl $5, %0" - :"=a"(e) - } - } - assert e == 5 -*/ -} diff --git a/vlib/v/tests/assembly/asm_test.amd64.v b/vlib/v/tests/assembly/asm_test.amd64.v new file mode 100644 index 0000000000..bd0679ed02 --- /dev/null +++ b/vlib/v/tests/assembly/asm_test.amd64.v @@ -0,0 +1,99 @@ +import v.tests.assembly.util + +fn test_inline_asm() { + a, mut b := 10, 0 + asm amd64 { + mov rax, a + mov b, rax + ; +r (b) + ; r (a) + ; rax + } + assert a == 10 + assert b == 10 + + mut c := 0 + asm amd64 { + mov c, 5 + ; +r (c) + } + assert c == 5 + + d, e, mut f := 10, 2, 0 + asm amd64 { + mov f, d + add f, e + add f, 5 + ; +r (f) // output + ; r (d) + r (e) // input + } + assert d == 10 + assert e == 2 + assert f == 17 + + // g, h, i := 2.3, 4.8, -3.5 + // asm rv64 { + // fadd.s $i, $g, $h // test `.` in instruction name + // : =r (i) as i + // : r (g) as g + // r (g) as h + // } + // assert g == 2.3 + // assert h == 4.8 + // assert i == 7.1 + + mut j := 0 + // do 5*3 + // adding three, five times + asm amd64 { + mov rcx, 5 // loop 5 times + loop_start: + add j, 3 + loop loop_start + ; +r (j) + ; ; rcx + } + assert j == 5 * 3 + + // k := 0 // Wait for tcc to implement goto, and gcc has odd errors + // mut loops := 0 + // outside_label: + // if k != 5 { + // loops++ + // asm goto amd64 { + // mov k, 1 + // mov k, 5 + // jmp outside_label + // ; =r (k) as k + // ; r (k) + // ; + // ; outside_label + // } + // } + // assert loops == 1 + // assert k == 5 + + // not marked as mut because we derefernce m to change l + l := 5 + m := &l + asm amd64 { + movq [m], 7 // have to specify size with q + ; ; r (m) + } + assert l == 7 + + // same as above + n := [5, 9, 0, 4] + asm amd64 { + loop_start2: + addq [in_data + rcx * 4 + 0], 2 + loop loop_start2 + addq [in_data + rcx * 4 + 0], 2 + ; ; c (n.len - 1) // c is counter (loop) register + r (n.data) as in_data + } + assert n == [7, 11, 2, 6] + + assert util.add(8, 9, 34, 7) == 58 // test .amd64.v files +} diff --git a/vlib/v/tests/assembly/asm_test.i386.v b/vlib/v/tests/assembly/asm_test.i386.v new file mode 100644 index 0000000000..aa1ec91110 --- /dev/null +++ b/vlib/v/tests/assembly/asm_test.i386.v @@ -0,0 +1,98 @@ +import v.tests.assembly.util +// rename this file to asm_test.amd64.v (and make more for other architectures) once pure v code is enforced + +fn test_inline_asm() { + a, mut b := 10, 0 + asm i386 { + mov eax, a + mov b, eax + ; +r (b) + ; r (a) + ; eax + } + assert a == 10 + assert b == 10 + + mut c := 0 + asm i386 { + mov c, 5 + ; +r (c) + } + assert c == 5 + + d, e, mut f := 10, 2, 0 + asm i386 { + mov f, d + add f, e + add f, 5 + ; +r (f) // output + ; r (d) + r (e) // input + } + assert d == 10 + assert e == 2 + assert f == 17 + + // g, h, i := 2.3, 4.8, -3.5 + // asm rv64 { + // fadd.s $i, $g, $h // test `.` in instruction name + // : =r (i) as i + // : r (g) as g + // r (g) as h + // } + // assert g == 2.3 + // assert h == 4.8 + // assert i == 7.1 + + mut j := 0 + // do 5*3 + // adding three, five times + asm i386 { + mov ecx, 5 // loop 5 times + loop_start: + add j, 3 + loop loop_start + ; +r (j) + ; ; ecx + } + assert j == 5 * 3 + + // k := 0 // Wait for tcc to implement goto, and gcc has odd errors + // mut loops := 0 + // outside_label: + // if k != 5 { + // loops++ + // asm goto amd64 { + // mov k, 1 + // mov k, 5 + // jmp outside_label + // ; =r (k) as k + // ; r (k) + // ; + // ; outside_label + // } + // } + // assert loops == 1 + // assert k == 5 + + // not marked as mut because we derefernce m to change l + l := 5 + m := &l + asm i386 { + movd [m], 7 // have to specify size with q + ; ; r (m) + } + assert l == 7 + + // same as above + n := [5, 9, 0, 4] + asm i386 { + loop_start2: + addd [in_data + ecx * 4 + 0], 2 + loop loop_start2 + addd [in_data + ecx * 4 + 0], 2 + ; ; c (n.len - 1) // c is counter (loop) register + r (n.data) as in_data + } + assert n == [7, 11, 2, 6] +} diff --git a/vlib/v/tests/assembly/util/dot_amd64_util.amd64.v b/vlib/v/tests/assembly/util/dot_amd64_util.amd64.v new file mode 100644 index 0000000000..7cf5fd5125 --- /dev/null +++ b/vlib/v/tests/assembly/util/dot_amd64_util.amd64.v @@ -0,0 +1,15 @@ +module util + +pub fn add(a ...int) int { + mut res := 0 + asm amd64 { + loop_start3: + addq rax, [in_data + rcx * 4 + 0] + loop loop_start3 + addq rax, [in_data + rcx * 4 + 0] + ; +a (res) + ; c (a.len - 1) // c is counter (loop) register + r (a.data) as in_data + } + return res +}