optimized ecall handling

This commit is contained in:
Luxferre
2026-06-18 20:15:48 +03:00
parent b0caa63ea1
commit ce40bb9a28
2 changed files with 138 additions and 176 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ A-machine is a simple and straightforward implementation of the RV32I specificat
- [amach.awk](amach.awk), the emulator core itself that supports .dec files, - [amach.awk](amach.awk), the emulator core itself that supports .dec files,
- and [amach](amach) shell script which is a two-liner that uses POSIX `od` to convert .bin files into .dec files on the fly and then passes them to the emulator core. - and [amach](amach) shell script which is a two-liner that uses POSIX `od` to convert .bin files into .dec files on the fly and then passes them to the emulator core.
Additionally, A-machine is shipped with a reference test suite that assumes that you have a full RISC-V toolchain installed under the `riscv64-linux-gnu-` prefix (adjustable in the `Makefile`). Just run `make run` to build and run all tests. In case you don't have any toolchain available, two files called `rv32imac_tests.bin` and `rv32imac_syscall_tests.bin` are also included into the repo. Additionally, A-machine is shipped with a reference test suite that assumes that you have a full RISC-V toolchain installed under the `riscv64-linux-gnu-` prefix (adjustable in the `Makefile`). Just run `make run` to build and run all tests.
The project is to be considered highly experimental and not production-ready in any way. The project is to be considered highly experimental and not production-ready in any way.
+137 -175
View File
@@ -224,254 +224,216 @@ function amach_exit(code, fd) {
# syscall emulation # syscall emulation
function handle_ecall(callnum, a0, a1, a2, a3, a4, a5, i, l, buf, path, pathname, flags, fd, count, now_time, dtype, full_path, reclen, bytes_written, idx, count_limit, buf_addr, name, status, follow, oldpath, newpath, access_mode, is_read, is_write, do_trunc, cmd, line, arr, size, buflen) { function handle_ecall(callnum, a0, a1, a2, a3, a4, a5, i, reclen, cmd, line, arr) {
if(callnum == 34) { # sys_mkdirat if(callnum == 34) { # sys_mkdirat
pathname = read_str(a1) a1 = resolve_path(a0, read_str(a1))
path = resolve_path(a0, pathname) if(path_exists(a1)) setreg(10, -17) # -EEXIST
if(path_exists(path)) setreg(10, -17) # -EEXIST else setreg(10, (system("mkdir " qquote(a1)) == 0) ? 0 : -2)
else {
status = system("mkdir " qquote(path))
setreg(10, (status == 0) ? 0 : -2)
}
} else if(callnum == 35) { # sys_unlinkat } else if(callnum == 35) { # sys_unlinkat
pathname = read_str(a1) a1 = resolve_path(a0, read_str(a1))
path = resolve_path(a0, pathname) if(!path_exists(a1)) setreg(10, -2) # -ENOENT
if(!path_exists(path)) { else {
setreg(10, -2) # -ENOENT a2 = bw_and(a2, 512) ? "rmdir " : "rm "
} else { setreg(10, (system(a2 qquote(a1)) == 0) ? 0 : -1)
if(bw_and(a2, 512)) # AT_REMOVEDIR (0x200)
status = system("rmdir " qquote(path))
else
status = system("rm " qquote(path))
setreg(10, (status == 0) ? 0 : -1)
} }
} else if(callnum == 38 || callnum == 276) { # sys_renameat / sys_renameat2 } else if(callnum == 38 || callnum == 276) { # sys_renameat / sys_renameat2
oldpath = resolve_path(a0, read_str(a1)) a1 = resolve_path(a0, read_str(a1))
newpath = resolve_path(a2, read_str(a3)) a3 = resolve_path(a2, read_str(a3))
status = system("mv " qquote(oldpath) " " qquote(newpath)) setreg(10, (system("mv " qquote(a1) " " qquote(a3)) == 0) ? 0 : -1)
setreg(10, (status == 0) ? 0 : -1)
} else if(callnum == 56) { # sys_openat } else if(callnum == 56) { # sys_openat
pathname = read_str(a1) a1 = resolve_path(a0, read_str(a1))
path = resolve_path(a0, pathname) if(bw_and(a2, 64)) { # O_CREAT (0x40)
flags = a2 if(bw_and(a2, 128) && path_exists(a1)) { # O_EXCL (0x80)
if(bw_and(flags, 64)) { # O_CREAT (0x40)
if(bw_and(flags, 128) && path_exists(path)) { # O_EXCL (0x80)
setreg(10, -17) # -EEXIST setreg(10, -17) # -EEXIST
return return
} }
if(!path_exists(path)) { if(!path_exists(a1)) {
printf("") > path printf("") > a1
close(path) close(a1)
}
} else {
if(!path_exists(path)) {
setreg(10, -2) # -ENOENT
return
} }
} else if(!path_exists(a1)) {
setreg(10, -2) # -ENOENT
return
} }
fd = NEXT_FD++ a3 = NEXT_FD++
FD_PATH[fd] = path FD_PATH[a3] = a1
FD_FLAGS[fd] = flags FD_FLAGS[a3] = a2
FD_OFFSET[fd] = 0 FD_OFFSET[a3] = 0
FD_SIZE[fd] = 0 FD_SIZE[a3] = 0
FD_DIRTY[fd] = 0 FD_DIRTY[a3] = 0
if(is_dir(path)) { if(is_dir(a1)) {
cmd = "ls -a1 " qquote(path) cmd = "ls -a1 " qquote(a1)
count = 0 a4 = 0
while((cmd | getline line) > 0) while((cmd | getline line) > 0)
if(line != "") FD_DIRENTS[fd, count++] = line if(line != "") FD_DIRENTS[a3, a4++] = line
close(cmd) close(cmd)
FD_DIRENT_COUNT[fd] = count FD_DIRENT_COUNT[a3] = a4
} else { } else {
access_mode = bw_and(flags, 3) a4 = bw_and(a2, 3) # access_mode
is_read = (access_mode == 0 || access_mode == 2) a5 = (a4 != 0) && bw_and(a2, 512) # do_trunc
is_write = (access_mode == 1 || access_mode == 2) if(a4 != 1 && !a5) { # is_read && !do_trunc
do_trunc = (is_write && bw_and(flags, 512)) # O_TRUNC (0x200) cmd = "od -An -v -tu1 " qquote(a1)
a0 = 0 # reuse a0 for size
if(is_read && !do_trunc) {
cmd = "od -An -v -tu1 " qquote(path)
size = 0
while((cmd | getline line) > 0) { while((cmd | getline line) > 0) {
split(line, arr) split(line, arr)
for(i = 1; i in arr; i++) for(i = 1; i in arr; i++)
FD_DATA[fd, size++] = arr[i] FD_DATA[a3, a0++] = arr[i]
} }
close(cmd) close(cmd)
FD_SIZE[fd] = size FD_SIZE[a3] = a0
} else if(do_trunc) { } else if(a5) {
printf("") > path printf("") > a1
close(path) close(a1)
} }
} }
setreg(10, fd) setreg(10, a3)
} else if(callnum == 57) { # sys_close } else if(callnum == 57) { # sys_close
fd = a0 if(a0 >= 3 && (a0 in FD_PATH)) {
if(fd >= 3 && (fd in FD_PATH)) { if(FD_DIRTY[a0]) write_fd_to_file(a0)
if(FD_DIRTY[fd]) write_fd_to_file(fd) delete FD_PATH[a0]
delete FD_PATH[fd] delete FD_FLAGS[a0]
delete FD_FLAGS[fd] delete FD_OFFSET[a0]
delete FD_OFFSET[fd] delete FD_SIZE[a0]
delete FD_SIZE[fd] delete FD_DIRTY[a0]
delete FD_DIRTY[fd] if(a0 in FD_DIRENT_COUNT) {
if(fd in FD_DIRENT_COUNT) { for(i = 0; i < FD_DIRENT_COUNT[a0]; i++) delete FD_DIRENTS[a0, i]
for(i = 0; i < FD_DIRENT_COUNT[fd]; i++) delete FD_DIRENTS[fd, i] delete FD_DIRENT_COUNT[a0]
delete FD_DIRENT_COUNT[fd] } else for(i = 0; i < FD_SIZE[a0]; i++) delete FD_DATA[a0, i]
} else for(i = 0; i < FD_SIZE[fd]; i++) delete FD_DATA[fd, i]
setreg(10, 0) setreg(10, 0)
} else setreg(10, -9) # -EBADF } else setreg(10, -9) # -EBADF
} else if(callnum == 61) { # sys_getdents64 } else if(callnum == 61) { # sys_getdents64
fd = a0 if(!(a0 in FD_DIRENT_COUNT)) setreg(10, -9) # -EBADF
if(!(fd in FD_DIRENT_COUNT)) setreg(10, -9) # -EBADF
else { else {
bytes_written = 0 a3 = 0 # bytes_written
idx = FD_OFFSET[fd] a4 = FD_OFFSET[a0] # idx
count_limit = a2 while(a4 < FD_DIRENT_COUNT[a0]) {
buf_addr = a1 a5 = FD_DIRENTS[a0, a4] # name
while(idx < FD_DIRENT_COUNT[fd]) { reclen = int((20 + length(a5) + 7) / 8) * 8
name = FD_DIRENTS[fd, idx] if(a3 + reclen > a2) { # count_limit
reclen = int((20 + length(name) + 7) / 8) * 8 if(a3 == 0) {
if(bytes_written + reclen > count_limit) {
if(bytes_written == 0) {
setreg(10, -22) # -EINVAL setreg(10, -22) # -EINVAL
return return
} }
break break
} }
write_mem(buf_addr + bytes_written, 1, 8) write_mem(a1 + a3, 1, 8)
write_mem(buf_addr + bytes_written + 8, idx + 1, 8) write_mem(a1 + a3 + 8, a4 + 1, 8)
write_mem(buf_addr + bytes_written + 16, reclen, 2) write_mem(a1 + a3 + 16, reclen, 2)
full_path = FD_PATH[fd] "/" name write_mem(a1 + a3 + 18, is_dir(FD_PATH[a0] "/" a5) ? 4 : 8, 1) # dtype
dtype = is_dir(full_path) ? 4 : 8 for(i = 0; i < length(a5); i++)
write_mem(buf_addr + bytes_written + 18, dtype, 1) MEM[a1 + a3 + 19 + i] = ord(substr(a5, i + 1, 1))
for(i = 0; i < length(name); i++) MEM[a1 + a3 + 19 + length(a5)] = 0
MEM[buf_addr + bytes_written + 19 + i] = ord(substr(name, i + 1, 1)) for(i = 19 + length(a5) + 1; i < reclen; i++)
MEM[buf_addr + bytes_written + 19 + length(name)] = 0 MEM[a1 + a3 + i] = 0
for(i = 19 + length(name) + 1; i < reclen; i++) a3 += reclen
MEM[buf_addr + bytes_written + i] = 0 a4++
bytes_written += reclen
idx++
} }
FD_OFFSET[fd] = idx FD_OFFSET[a0] = a4
setreg(10, bytes_written) setreg(10, a3)
} }
} else if(callnum == 62) { # sys_lseek } else if(callnum == 62) { # sys_lseek
fd = a0 if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) {
if(fd >= 3 && (fd in FD_PATH) && !(fd in FD_DIRENT_COUNT)) { if(a2 == 0) {} # SEEK_SET, a1 is unchanged
idx = FD_OFFSET[fd] else if(a2 == 1) a1 += FD_OFFSET[a0]
size = FD_SIZE[fd] else if(a2 == 2) a1 += FD_SIZE[a0]
if(a2 == 0) idx = a1 # SEEK_SET
else if(a2 == 1) idx = idx + a1 # SEEK_CUR
else if(a2 == 2) idx = size + a1 # SEEK_END
else { else {
setreg(10, -22) # -EINVAL setreg(10, -22) # -EINVAL
return return
} }
if(idx < 0) setreg(10, -22) # -EINVAL if(a1 < 0) setreg(10, -22) # -EINVAL
else { else {
FD_OFFSET[fd] = idx FD_OFFSET[a0] = a1
setreg(10, idx) setreg(10, a1)
} }
} else setreg(10, -9) # -EBADF } else setreg(10, -9) # -EBADF
} else if(callnum == 63) { # sys_read } else if(callnum == 63) { # sys_read
fd = a0 if(a0 == 0) {
if(fd == 0) { getline a3 < "/dev/tty"
getline buf < "/dev/tty" a4 = length(a3); if(a4 > a2) a4 = a2
l = length(buf); if(l > a2) l = a2 for(i = 0; i < a4; i++) MEM[uint32(a1 + i)] = ord(substr(a3, i + 1, 1))
for(i = 0; i < l; i++) MEM[uint32(a1 + i)] = ord(substr(buf, i + 1, 1)) setreg(10, a4)
setreg(10, l) } else if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) {
} else if(fd >= 3 && (fd in FD_PATH) && !(fd in FD_DIRENT_COUNT)) { a3 = FD_OFFSET[a0] # idx
idx = FD_OFFSET[fd] a4 = FD_SIZE[a0] # size
size = FD_SIZE[fd] if(a3 >= a4) setreg(10, 0)
count = a2
if(idx >= size) setreg(10, 0)
else { else {
if(idx + count > size) count = size - idx if(a3 + a2 > a4) a2 = a4 - a3 # count
for(i = 0; i < count; i++) for(i = 0; i < a2; i++)
MEM[uint32(a1 + i)] = FD_DATA[fd, idx + i] MEM[uint32(a1 + i)] = FD_DATA[a0, a3 + i]
FD_OFFSET[fd] = idx + count FD_OFFSET[a0] = a3 + a2
setreg(10, count) setreg(10, a2)
} }
} else setreg(10, -9) # -EBADF } else setreg(10, -9) # -EBADF
} else if(callnum == 64) { # sys_write } else if(callnum == 64) { # sys_write
fd = a0 if(a0 == 1 || a0 == 2) {
if(fd == 1 || fd == 2) {
for(i = 0; i < a2; i++) printf("%c", MEM[uint32(a1 + i)]) for(i = 0; i < a2; i++) printf("%c", MEM[uint32(a1 + i)])
setreg(10, a2) setreg(10, a2)
} else if(fd >= 3 && (fd in FD_PATH) && !(fd in FD_DIRENT_COUNT)) { } else if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) {
idx = FD_OFFSET[fd] a3 = bw_and(FD_FLAGS[a0], 1024) ? FD_SIZE[a0] : FD_OFFSET[a0] # idx
count = a2 for(i = 0; i < a2; i++)
if(bw_and(FD_FLAGS[fd], 1024)) idx = FD_SIZE[fd] # O_APPEND (0x400) FD_DATA[a0, a3 + i] = MEM[uint32(a1 + i)]
for(i = 0; i < count; i++) FD_OFFSET[a0] = a3 + a2
FD_DATA[fd, idx + i] = MEM[uint32(a1 + i)] if(FD_OFFSET[a0] > FD_SIZE[a0]) FD_SIZE[a0] = FD_OFFSET[a0]
FD_OFFSET[fd] = idx + count FD_DIRTY[a0] = 1
if(FD_OFFSET[fd] > FD_SIZE[fd]) FD_SIZE[fd] = FD_OFFSET[fd] setreg(10, a2)
FD_DIRTY[fd] = 1
setreg(10, count)
} else setreg(10, -9) # -EBADF } else setreg(10, -9) # -EBADF
} else if(callnum == 78) { # sys_readlinkat } else if(callnum == 78) { # sys_readlinkat
path = resolve_path(a0, read_str(a1)) a0 = resolve_path(a0, read_str(a1))
cmd = "readlink " qquote(path) a1 = "readlink " qquote(a0)
if((cmd | getline line) > 0) { if((a1 | getline line) > 0) {
close(cmd) close(a1)
l = length(line) a4 = length(line)
if(l > a3) l = a3 if(a4 > a3) a4 = a3
for(i = 0; i < l; i++) for(i = 0; i < a4; i++)
MEM[uint32(a2 + i)] = ord(substr(line, i + 1, 1)) MEM[uint32(a2 + i)] = ord(substr(line, i + 1, 1))
setreg(10, l) setreg(10, a4)
} else { } else {
close(cmd) close(a1)
setreg(10, -22) # -EINVAL setreg(10, -22) # -EINVAL
} }
} else if(callnum == 79) { # sys_newfstatat } else if(callnum == 79) { # sys_newfstatat
pathname = read_str(a1) a1 = read_str(a1)
flags = a3 if(a1 == "" && bw_and(a3, 4096)) { # AT_EMPTY_PATH
if(pathname == "" && bw_and(flags, 4096)) { # AT_EMPTY_PATH if(a0 == -100) a0 = "."
if(a0 == -100) path = "." else if(a0 in FD_PATH) a0 = FD_PATH[a0]
else if(a0 in FD_PATH) path = FD_PATH[a0]
else { else {
setreg(10, -9) # -EBADF setreg(10, -9) # -EBADF
return return
} }
} else path = resolve_path(a0, pathname) } else a0 = resolve_path(a0, a1)
follow = (bw_and(flags, 256) == 0) # follow link if AT_SYMLINK_NOFOLLOW (0x100) is NOT set a4 = (bw_and(a3, 256) == 0) # follow link if AT_SYMLINK_NOFOLLOW is NOT set
status = fill_stat_struct(path, a2, follow) setreg(10, fill_stat_struct(a0, a2, a4))
setreg(10, status)
} else if(callnum == 80) { # sys_fstat } else if(callnum == 80) { # sys_fstat
fd = a0 if(a0 == 0) a0 = "/dev/stdin"
if(fd == 0) path = "/dev/stdin" else if(a0 == 1) a0 = "/dev/stdout"
else if(fd == 1) path = "/dev/stdout" else if(a0 == 2) a0 = "/dev/stderr"
else if(fd == 2) path = "/dev/stderr" else if(a0 >= 3 && (a0 in FD_PATH)) a0 = FD_PATH[a0]
else if(fd >= 3 && (fd in FD_PATH)) path = FD_PATH[fd]
else { else {
setreg(10, -9) # -EBADF setreg(10, -9) # -EBADF
return return
} }
status = fill_stat_struct(path, a1, 1) setreg(10, fill_stat_struct(a0, a1, 1))
setreg(10, status)
} else if(callnum == 93 || callnum == 94) { # sys_exit / sys_exit_group } else if(callnum == 93 || callnum == 94) { # sys_exit / sys_exit_group
amach_exit(a0) amach_exit(a0)
} else if(callnum == 113 || callnum == 403) { # sys_clock_gettime / sys_clock_gettime64 } else if(callnum == 113 || callnum == 403) { # sys_clock_gettime / sys_clock_gettime64
get_system_time(now_time) get_system_time(arr)
if(callnum == 113) { if(callnum == 113) {
write_mem(a1, now_time["sec"], 4) write_mem(a1, arr["sec"], 4)
write_mem(a1 + 4, now_time["nsec"], 4) write_mem(a1 + 4, arr["nsec"], 4)
} else { } else {
write_mem(a1, now_time["sec"], 8) write_mem(a1, arr["sec"], 8)
write_mem(a1 + 8, now_time["nsec"], 4) write_mem(a1 + 8, arr["nsec"], 4)
} }
setreg(10, 0) setreg(10, 0)
} else if(callnum == 169) { # sys_gettimeofday } else if(callnum == 169) { # sys_gettimeofday
get_system_time(now_time) get_system_time(arr)
write_mem(a0, now_time["sec"], 4) write_mem(a0, arr["sec"], 4)
write_mem(a0 + 4, int(now_time["nsec"] / 1000), 4) write_mem(a0 + 4, int(arr["nsec"] / 1000), 4)
setreg(10, 0) setreg(10, 0)
} else if(callnum == 278) { # sys_getrandom } else if(callnum == 278) { # sys_getrandom
buf_addr = a0 for(i = 0; i < a1; i++)
buflen = a1 MEM[uint32(a0 + i)] = int(rand() * 256)
for(i = 0; i < buflen; i++) setreg(10, a1)
MEM[uint32(buf_addr + i)] = int(rand() * 256)
setreg(10, buflen)
} else trapout(sprintf("Unimplemented environment call %d at 0x%X", callnum, pc - 4)) } else trapout(sprintf("Unimplemented environment call %d at 0x%X", callnum, pc - 4))
} }