all: reimplement inline assembly (#8645)

pull/9343/head
crthpl 2021-03-16 17:43:17 -07:00 committed by GitHub
parent dd9f9c2718
commit fafb035fb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2089 additions and 335 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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
}
<!-- ignore because it doesn't pass fmt test (why?) -->
```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.

16
examples/asm.v 100644
View File

@ -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
}

View File

@ -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

View File

@ -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/

View File

@ -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
}

View File

@ -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 {

View File

@ -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 | }

View File

@ -0,0 +1,3 @@
asm amd64 {
mov ebx, a
}

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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{}

View File

@ -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

View File

@ -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() {

View File

@ -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 == '' {

View File

@ -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'

View File

@ -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`.

View File

@ -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))
}
}
}

View File

@ -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.

View File

@ -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
*/
}

View File

@ -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
}

View File

@ -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]
}

View File

@ -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
}