Files
a-machine/amach.awk
T

794 lines
28 KiB
Awk
Raw Normal View History

2026-06-18 14:01:36 +03:00
#!/usr/bin/env awk -f
2026-06-18 19:10:32 +03:00
# A-Machine: an experimental RISC-V (RV32IMAC) emulator in POSIX AWK
2026-06-18 14:01:36 +03:00
# with a very small subset of supported ECALLs
# Accepts a headerless binary previously converted to .dec format
# e.g. with POSIX od: od -An -v -tu1 program.bin > program.dec
# Usage: awk -f amach.awk [-v LVA=...] -- prog.dec
# Or run a .bin directly with:
# od -An -v -tu1 program.bin | awk -f amach.awk [-v LVA=...]
# Created by Luxferre in 2026, released into the public domain
# fatal error reporting function
2026-06-18 19:54:49 +03:00
function trapout(msg, fd) {
2026-06-18 20:10:23 +03:00
for(fd in FD_PATH) if(FD_DIRTY[fd]) write_fd_to_file(fd)
2026-06-18 14:01:36 +03:00
cmd = "cat 1>&2"
printf("Fatal: %s\n", msg) | cmd
close(cmd)
exit(1)
}
# helper functions
2026-06-18 19:23:14 +03:00
function uint32(val) {
val = int(val) % 4294967296
return (val < 0) ? (val + 4294967296) : val
}
2026-06-18 14:01:36 +03:00
function setreg(idx, val) {
if(idx > 0) {
2026-06-18 19:23:14 +03:00
val = uint32(val)
2026-06-18 14:01:36 +03:00
REG[idx] = (val >= 2147483648) ? (val - 4294967296) : val
}
}
function getreg(idx) {return (idx == 0) ? 0 : int(REG[idx])}
function imm_sign_ex(val) {val = int(val); return (val >= 2048) ? (val - 4096) : val}
function imm_sign_ex_b(val) {val = int(val); return (val >= 4096) ? (val - 8192) : val}
function floor(x, i) {
i = int(x)
return (x >= 0 || x == i) ? i : (i - 1)
}
function ord(c, b) {
if(!TGL_ORD["#"]) for(b=0;b<256;b++) TGL_ORD[sprintf("%c", b)] = b
return int(TGL_ORD[c])
}
2026-06-18 19:23:14 +03:00
function bw_op(a, b, op, v, r) {
v = 1; r = 0; a = uint32(a); b = uint32(b)
2026-06-18 14:01:36 +03:00
while(a > 0 || b > 0) {
2026-06-18 19:23:14 +03:00
if(op == "&" && (a%2) == 1 && (b%2) == 1) r += v
else if(op == "|" && ((a%2) == 1 || (b%2) == 1)) r += v
else if(op == "^" && (a%2) != (b%2)) r += v
2026-06-18 15:36:24 +03:00
a = int(a/2); b = int(b/2); v *= 2
2026-06-18 14:01:36 +03:00
}
return int(r)
}
2026-06-18 19:23:14 +03:00
function bw_and(a, b) {return bw_op(a, b, "&")}
function bw_or(a, b) {return bw_op(a, b, "|")}
function bw_xor(a, b) {return bw_op(a, b, "^")}
function read_mem(addr, bytes, signed, i, val, max_val) {
addr = uint32(addr)
val = 0
for(i=0; i<bytes; i++) val += MEM[(addr+i)%4294967296] * (256^i)
if(signed) {
max_val = 256^bytes
if(val >= max_val/2) val -= max_val
2026-06-18 14:01:36 +03:00
}
2026-06-18 19:23:14 +03:00
return val
2026-06-18 14:01:36 +03:00
}
2026-06-18 19:23:14 +03:00
function write_mem(addr, val, bytes, i) {
addr = uint32(addr)
val = uint32(val)
for(i=0; i<bytes; i++) MEM[(addr+i)%4294967296] = int(val / (256^i)) % 256
2026-06-18 14:01:36 +03:00
}
2026-06-18 19:23:14 +03:00
function read_word(addr) {return read_mem(addr, 4, 1)}
function write_word(addr, val) {write_mem(addr, val, 4)}
2026-06-18 14:01:36 +03:00
2026-06-18 19:54:49 +03:00
# Escapes single quotes for shell command safety
function qquote(str) {
gsub(/'/, "'\\''", str)
return "'" str "'"
}
# Converts hex string to decimal
function hex_to_dec(hex, dec, i, len, c, val) {
dec = 0
len = length(hex)
2026-06-18 20:10:23 +03:00
for(i = 1; i <= len; i++) {
2026-06-18 19:54:49 +03:00
c = tolower(substr(hex, i, 1))
2026-06-18 20:10:23 +03:00
if(c ~ /[0-9]/) val = int(c)
else if(c == "a") val = 10
else if(c == "b") val = 11
else if(c == "c") val = 12
else if(c == "d") val = 13
else if(c == "e") val = 14
else if(c == "f") val = 15
2026-06-18 19:54:49 +03:00
else continue
dec = dec * 16 + val
}
return dec
}
# Reads a null-terminated string from memory
function read_str(addr, c, s) {
s = ""
addr = uint32(addr)
2026-06-18 20:10:23 +03:00
while((c = MEM[addr++]) != 0) s = s sprintf("%c", c)
2026-06-18 19:54:49 +03:00
return s
}
# Checks if path exists using POSIX test -e or test -L
function path_exists(path) {
return (system("test -e " qquote(path)) == 0 || system("test -L " qquote(path)) == 0)
}
# Checks if path is a directory using POSIX test -d
2026-06-18 20:10:23 +03:00
function is_dir(path) {return (system("test -d " qquote(path)) == 0)}
2026-06-18 19:54:49 +03:00
# Resolves relative path against dfd directory
function resolve_path(dfd, pathname, path) {
2026-06-18 20:10:23 +03:00
if(substr(pathname, 1, 1) == "/") return pathname
if(dfd == -100) return pathname
if(dfd in FD_PATH) return FD_PATH[dfd] "/" pathname
2026-06-18 19:54:49 +03:00
return pathname
}
# Populate timespec structure
# TV_SEC: 64-bit for clock_gettime64, 32-bit for clock_gettime
function get_system_time(time_arr, cmd, line) {
cmd = "date +%s%N"
2026-06-18 20:10:23 +03:00
if((cmd | getline line) > 0) {
if(length(line) > 9 && line ~ /^[0-9]+$/) {
2026-06-18 19:54:49 +03:00
time_arr["sec"] = int(substr(line, 1, length(line) - 9))
time_arr["nsec"] = int(substr(line, length(line) - 8))
close(cmd)
return
}
}
close(cmd)
# Fallback if %N is not supported
cmd = "date +%s"
2026-06-18 20:10:23 +03:00
if((cmd | getline line) > 0) {
2026-06-18 19:54:49 +03:00
time_arr["sec"] = int(line)
2026-06-18 20:10:23 +03:00
} else time_arr["sec"] = 0
2026-06-18 19:54:49 +03:00
close(cmd)
2026-06-18 20:10:23 +03:00
if(time_arr["sec"] == LAST_SEC) {
2026-06-18 19:54:49 +03:00
VIRT_NSEC += 500000
2026-06-18 20:10:23 +03:00
if(VIRT_NSEC >= 1000000000) {
2026-06-18 19:54:49 +03:00
VIRT_NSEC = 999999999
}
} else {
LAST_SEC = time_arr["sec"]
VIRT_NSEC = 0
}
time_arr["nsec"] = VIRT_NSEC
}
# Fills stat structure in VM memory
function fill_stat_struct(path, addr, follow, cmd, line, parts, opt, mode_hex, mode_dec, size, atime, mtime, ctime, i) {
opt = follow ? "-L " : ""
cmd = "stat " opt "-c \"%f %s %X %Y %Z\" " qquote(path)
2026-06-18 20:10:23 +03:00
if((cmd | getline line) > 0) {
2026-06-18 19:54:49 +03:00
split(line, parts)
mode_hex = parts[1]
mode_dec = hex_to_dec(mode_hex)
size = int(parts[2])
atime = int(parts[3])
mtime = int(parts[4])
ctime = int(parts[5])
close(cmd)
2026-06-18 20:10:23 +03:00
for(i = 0; i < 104; i++) MEM[addr + i] = 0
2026-06-18 19:54:49 +03:00
write_mem(addr + 0, 1, 8)
write_mem(addr + 8, 1, 8)
write_mem(addr + 16, mode_dec, 4)
write_mem(addr + 20, 1, 4)
write_mem(addr + 24, 1000, 4)
write_mem(addr + 28, 1000, 4)
write_mem(addr + 32, 0, 8)
write_mem(addr + 48, size, 8)
write_mem(addr + 56, 4096, 4)
write_mem(addr + 64, int((size + 511) / 512), 8)
write_mem(addr + 72, atime, 4)
write_mem(addr + 80, mtime, 4)
write_mem(addr + 88, ctime, 4)
return 0
} else {
close(cmd)
return -2
}
}
# Writes file descriptor cached data back to file
function write_fd_to_file(fd, path, chunk, i, size) {
path = FD_PATH[fd]
size = FD_SIZE[fd]
printf("") > path
close(path)
chunk = ""
2026-06-18 20:10:23 +03:00
for(i = 0; i < size; i++) {
2026-06-18 19:54:49 +03:00
chunk = chunk sprintf("%c", FD_DATA[fd, i])
2026-06-18 20:10:23 +03:00
if(length(chunk) >= 1024) {
2026-06-18 19:54:49 +03:00
printf("%s", chunk) >> path
chunk = ""
}
}
2026-06-18 20:10:23 +03:00
if(length(chunk) > 0) printf("%s", chunk) >> path
2026-06-18 19:54:49 +03:00
close(path)
}
function amach_exit(code, fd) {
2026-06-18 20:10:23 +03:00
for(fd in FD_PATH) if(FD_DIRTY[fd]) write_fd_to_file(fd)
2026-06-18 19:54:49 +03:00
exit(code)
}
2026-06-18 14:01:36 +03:00
# syscall emulation
2026-06-18 20:15:48 +03:00
function handle_ecall(callnum, a0, a1, a2, a3, a4, a5, i, reclen, cmd, line, arr) {
2026-06-18 20:10:23 +03:00
if(callnum == 34) { # sys_mkdirat
2026-06-18 20:15:48 +03:00
a1 = resolve_path(a0, read_str(a1))
if(path_exists(a1)) setreg(10, -17) # -EEXIST
else setreg(10, (system("mkdir " qquote(a1)) == 0) ? 0 : -2)
2026-06-18 20:10:23 +03:00
} else if(callnum == 35) { # sys_unlinkat
2026-06-18 20:15:48 +03:00
a1 = resolve_path(a0, read_str(a1))
if(!path_exists(a1)) setreg(10, -2) # -ENOENT
else {
a2 = bw_and(a2, 512) ? "rmdir " : "rm "
setreg(10, (system(a2 qquote(a1)) == 0) ? 0 : -1)
2026-06-18 20:10:23 +03:00
}
} else if(callnum == 38 || callnum == 276) { # sys_renameat / sys_renameat2
2026-06-18 20:15:48 +03:00
a1 = resolve_path(a0, read_str(a1))
a3 = resolve_path(a2, read_str(a3))
setreg(10, (system("mv " qquote(a1) " " qquote(a3)) == 0) ? 0 : -1)
2026-06-18 20:10:23 +03:00
} else if(callnum == 56) { # sys_openat
2026-06-18 20:15:48 +03:00
a1 = resolve_path(a0, read_str(a1))
if(bw_and(a2, 64)) { # O_CREAT (0x40)
if(bw_and(a2, 128) && path_exists(a1)) { # O_EXCL (0x80)
2026-06-18 19:54:49 +03:00
setreg(10, -17) # -EEXIST
return
}
2026-06-18 20:15:48 +03:00
if(!path_exists(a1)) {
printf("") > a1
close(a1)
2026-06-18 19:54:49 +03:00
}
2026-06-18 20:15:48 +03:00
} else if(!path_exists(a1)) {
setreg(10, -2) # -ENOENT
return
2026-06-18 19:54:49 +03:00
}
2026-06-18 20:15:48 +03:00
a3 = NEXT_FD++
FD_PATH[a3] = a1
FD_FLAGS[a3] = a2
FD_OFFSET[a3] = 0
FD_SIZE[a3] = 0
FD_DIRTY[a3] = 0
if(is_dir(a1)) {
cmd = "ls -a1 " qquote(a1)
a4 = 0
2026-06-18 20:10:23 +03:00
while((cmd | getline line) > 0)
2026-06-18 20:15:48 +03:00
if(line != "") FD_DIRENTS[a3, a4++] = line
2026-06-18 19:54:49 +03:00
close(cmd)
2026-06-18 20:15:48 +03:00
FD_DIRENT_COUNT[a3] = a4
2026-06-18 19:54:49 +03:00
} else {
2026-06-18 20:15:48 +03:00
a4 = bw_and(a2, 3) # access_mode
a5 = (a4 != 0) && bw_and(a2, 512) # do_trunc
if(a4 != 1 && !a5) { # is_read && !do_trunc
cmd = "od -An -v -tu1 " qquote(a1)
a0 = 0 # reuse a0 for size
2026-06-18 20:10:23 +03:00
while((cmd | getline line) > 0) {
2026-06-18 19:54:49 +03:00
split(line, arr)
2026-06-18 20:10:23 +03:00
for(i = 1; i in arr; i++)
2026-06-18 20:15:48 +03:00
FD_DATA[a3, a0++] = arr[i]
2026-06-18 19:54:49 +03:00
}
close(cmd)
2026-06-18 20:15:48 +03:00
FD_SIZE[a3] = a0
} else if(a5) {
printf("") > a1
close(a1)
2026-06-18 19:54:49 +03:00
}
}
2026-06-18 20:15:48 +03:00
setreg(10, a3)
2026-06-18 20:10:23 +03:00
} else if(callnum == 57) { # sys_close
2026-06-18 20:15:48 +03:00
if(a0 >= 3 && (a0 in FD_PATH)) {
if(FD_DIRTY[a0]) write_fd_to_file(a0)
delete FD_PATH[a0]
delete FD_FLAGS[a0]
delete FD_OFFSET[a0]
delete FD_SIZE[a0]
delete FD_DIRTY[a0]
if(a0 in FD_DIRENT_COUNT) {
for(i = 0; i < FD_DIRENT_COUNT[a0]; i++) delete FD_DIRENTS[a0, i]
delete FD_DIRENT_COUNT[a0]
} else for(i = 0; i < FD_SIZE[a0]; i++) delete FD_DATA[a0, i]
2026-06-18 19:54:49 +03:00
setreg(10, 0)
2026-06-18 20:10:23 +03:00
} else setreg(10, -9) # -EBADF
} else if(callnum == 61) { # sys_getdents64
2026-06-18 20:15:48 +03:00
if(!(a0 in FD_DIRENT_COUNT)) setreg(10, -9) # -EBADF
2026-06-18 20:10:23 +03:00
else {
2026-06-18 20:15:48 +03:00
a3 = 0 # bytes_written
a4 = FD_OFFSET[a0] # idx
while(a4 < FD_DIRENT_COUNT[a0]) {
a5 = FD_DIRENTS[a0, a4] # name
reclen = int((20 + length(a5) + 7) / 8) * 8
if(a3 + reclen > a2) { # count_limit
if(a3 == 0) {
2026-06-18 19:54:49 +03:00
setreg(10, -22) # -EINVAL
return
}
break
}
2026-06-18 20:15:48 +03:00
write_mem(a1 + a3, 1, 8)
write_mem(a1 + a3 + 8, a4 + 1, 8)
write_mem(a1 + a3 + 16, reclen, 2)
write_mem(a1 + a3 + 18, is_dir(FD_PATH[a0] "/" a5) ? 4 : 8, 1) # dtype
for(i = 0; i < length(a5); i++)
MEM[a1 + a3 + 19 + i] = ord(substr(a5, i + 1, 1))
MEM[a1 + a3 + 19 + length(a5)] = 0
for(i = 19 + length(a5) + 1; i < reclen; i++)
MEM[a1 + a3 + i] = 0
a3 += reclen
a4++
2026-06-18 19:54:49 +03:00
}
2026-06-18 20:15:48 +03:00
FD_OFFSET[a0] = a4
setreg(10, a3)
2026-06-18 19:54:49 +03:00
}
2026-06-18 20:10:23 +03:00
} else if(callnum == 62) { # sys_lseek
2026-06-18 20:15:48 +03:00
if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) {
if(a2 == 0) {} # SEEK_SET, a1 is unchanged
else if(a2 == 1) a1 += FD_OFFSET[a0]
else if(a2 == 2) a1 += FD_SIZE[a0]
2026-06-18 19:54:49 +03:00
else {
setreg(10, -22) # -EINVAL
return
}
2026-06-18 20:15:48 +03:00
if(a1 < 0) setreg(10, -22) # -EINVAL
2026-06-18 20:10:23 +03:00
else {
2026-06-18 20:15:48 +03:00
FD_OFFSET[a0] = a1
setreg(10, a1)
2026-06-18 19:54:49 +03:00
}
2026-06-18 20:10:23 +03:00
} else setreg(10, -9) # -EBADF
} else if(callnum == 63) { # sys_read
2026-06-18 20:15:48 +03:00
if(a0 == 0) {
getline a3 < "/dev/tty"
a4 = length(a3); if(a4 > a2) a4 = a2
for(i = 0; i < a4; i++) MEM[uint32(a1 + i)] = ord(substr(a3, i + 1, 1))
setreg(10, a4)
} else if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) {
a3 = FD_OFFSET[a0] # idx
a4 = FD_SIZE[a0] # size
if(a3 >= a4) setreg(10, 0)
2026-06-18 20:10:23 +03:00
else {
2026-06-18 20:15:48 +03:00
if(a3 + a2 > a4) a2 = a4 - a3 # count
for(i = 0; i < a2; i++)
MEM[uint32(a1 + i)] = FD_DATA[a0, a3 + i]
FD_OFFSET[a0] = a3 + a2
setreg(10, a2)
2026-06-18 19:54:49 +03:00
}
2026-06-18 20:10:23 +03:00
} else setreg(10, -9) # -EBADF
} else if(callnum == 64) { # sys_write
2026-06-18 20:15:48 +03:00
if(a0 == 1 || a0 == 2) {
2026-06-18 20:10:23 +03:00
for(i = 0; i < a2; i++) printf("%c", MEM[uint32(a1 + i)])
2026-06-18 19:54:49 +03:00
setreg(10, a2)
2026-06-18 20:15:48 +03:00
} else if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) {
a3 = bw_and(FD_FLAGS[a0], 1024) ? FD_SIZE[a0] : FD_OFFSET[a0] # idx
for(i = 0; i < a2; i++)
FD_DATA[a0, a3 + i] = MEM[uint32(a1 + i)]
FD_OFFSET[a0] = a3 + a2
if(FD_OFFSET[a0] > FD_SIZE[a0]) FD_SIZE[a0] = FD_OFFSET[a0]
FD_DIRTY[a0] = 1
setreg(10, a2)
2026-06-18 20:10:23 +03:00
} else setreg(10, -9) # -EBADF
} else if(callnum == 78) { # sys_readlinkat
2026-06-18 20:15:48 +03:00
a0 = resolve_path(a0, read_str(a1))
a1 = "readlink " qquote(a0)
if((a1 | getline line) > 0) {
close(a1)
a4 = length(line)
if(a4 > a3) a4 = a3
for(i = 0; i < a4; i++)
2026-06-18 19:54:49 +03:00
MEM[uint32(a2 + i)] = ord(substr(line, i + 1, 1))
2026-06-18 20:15:48 +03:00
setreg(10, a4)
2026-06-18 19:54:49 +03:00
} else {
2026-06-18 20:15:48 +03:00
close(a1)
2026-06-18 19:54:49 +03:00
setreg(10, -22) # -EINVAL
}
2026-06-18 20:10:23 +03:00
} else if(callnum == 79) { # sys_newfstatat
2026-06-18 20:15:48 +03:00
a1 = read_str(a1)
if(a1 == "" && bw_and(a3, 4096)) { # AT_EMPTY_PATH
if(a0 == -100) a0 = "."
else if(a0 in FD_PATH) a0 = FD_PATH[a0]
2026-06-18 19:54:49 +03:00
else {
setreg(10, -9) # -EBADF
return
}
2026-06-18 20:15:48 +03:00
} else a0 = resolve_path(a0, a1)
a4 = (bw_and(a3, 256) == 0) # follow link if AT_SYMLINK_NOFOLLOW is NOT set
setreg(10, fill_stat_struct(a0, a2, a4))
2026-06-18 20:10:23 +03:00
} else if(callnum == 80) { # sys_fstat
2026-06-18 20:15:48 +03:00
if(a0 == 0) a0 = "/dev/stdin"
else if(a0 == 1) a0 = "/dev/stdout"
else if(a0 == 2) a0 = "/dev/stderr"
else if(a0 >= 3 && (a0 in FD_PATH)) a0 = FD_PATH[a0]
2026-06-18 19:54:49 +03:00
else {
setreg(10, -9) # -EBADF
return
}
2026-06-18 20:15:48 +03:00
setreg(10, fill_stat_struct(a0, a1, 1))
2026-06-18 20:10:23 +03:00
} else if(callnum == 93 || callnum == 94) { # sys_exit / sys_exit_group
2026-06-18 19:54:49 +03:00
amach_exit(a0)
2026-06-18 20:10:23 +03:00
} else if(callnum == 113 || callnum == 403) { # sys_clock_gettime / sys_clock_gettime64
2026-06-18 20:15:48 +03:00
get_system_time(arr)
2026-06-18 20:10:23 +03:00
if(callnum == 113) {
2026-06-18 20:15:48 +03:00
write_mem(a1, arr["sec"], 4)
write_mem(a1 + 4, arr["nsec"], 4)
2026-06-18 19:54:49 +03:00
} else {
2026-06-18 20:15:48 +03:00
write_mem(a1, arr["sec"], 8)
write_mem(a1 + 8, arr["nsec"], 4)
2026-06-18 19:54:49 +03:00
}
setreg(10, 0)
2026-06-18 20:10:23 +03:00
} else if(callnum == 169) { # sys_gettimeofday
2026-06-18 20:15:48 +03:00
get_system_time(arr)
write_mem(a0, arr["sec"], 4)
write_mem(a0 + 4, int(arr["nsec"] / 1000), 4)
2026-06-18 19:54:49 +03:00
setreg(10, 0)
2026-06-18 20:10:23 +03:00
} else if(callnum == 278) { # sys_getrandom
2026-06-18 20:15:48 +03:00
for(i = 0; i < a1; i++)
MEM[uint32(a0 + i)] = int(rand() * 256)
setreg(10, a1)
2026-06-18 20:10:23 +03:00
} else trapout(sprintf("Unimplemented environment call %d at 0x%X", callnum, pc - 4))
2026-06-18 14:01:36 +03:00
}
# instruction type executors
function amach_reg_arith(f3, f7, rd, rs1, rs2, r1, r2, ur1, ur2, shamt) {
2026-06-18 19:23:14 +03:00
r1 = getreg(rs1); r2 = getreg(rs2)
ur1 = uint32(r1); ur2 = uint32(r2)
2026-06-18 14:01:36 +03:00
shamt = ur2 % 32
if(f3 == 0 && f7 == 0) setreg(rd, r1 + r2)
else if(f3 == 0 && f7 == 32) setreg(rd, r1 - r2)
else if(f3 == 4 && f7 == 0) setreg(rd, bw_xor(r1, r2))
else if(f3 == 6 && f7 == 0) setreg(rd, bw_or(r1, r2))
else if(f3 == 7 && f7 == 0) setreg(rd, bw_and(r1, r2))
else if(f3 == 1 && f7 == 0) setreg(rd, (r1 * (2^shamt)) % 4294967296)
else if(f3 == 5 && f7 == 0) setreg(rd, int(ur1 / (2^shamt)))
else if(f3 == 5 && f7 == 32) setreg(rd, floor(r1 / (2^shamt)))
else if(f3 == 2 && f7 == 0) setreg(rd, (r1 < r2) ? 1 : 0)
else if(f3 == 3 && f7 == 0) setreg(rd, (ur1 < ur2) ? 1 : 0)
else if(f3 == 0 && f7 == 1) setreg(rd, (r1 * r2) % 4294967296)
else if(f3 == 1 && f7 == 1) setreg(rd, floor((r1 * r2) / 4294967296))
else if(f3 == 2 && f7 == 1) setreg(rd, floor((r1 * ur2) / 4294967296))
else if(f3 == 3 && f7 == 1) setreg(rd, floor((ur1 * ur2) / 4294967296))
else if(f3 == 4 && f7 == 1) setreg(rd, (r2 == 0) ? -1 : int(r1 / r2))
else if(f3 == 5 && f7 == 1) setreg(rd, (r2 == 0) ? 4294967295 : int(ur1 / ur2))
else if(f3 == 6 && f7 == 1) setreg(rd, (r2 == 0) ? r1 : (r1 % r2))
else if(f3 == 7 && f7 == 1) setreg(rd, (r2 == 0) ? r1 : (ur1 % ur2))
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
2026-06-18 19:23:14 +03:00
function amach_store(f3, rs1, rs2, immval, r1, r2) {
r1 = getreg(rs1); r2 = getreg(rs2)
if(f3 >= 0 && f3 <= 2) write_mem(r1 + immval, r2, 2^f3)
2026-06-18 14:01:36 +03:00
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
function amach_branch(f3, rs1, rs2, immval, r1, r2, ur1, ur2, taken) {
2026-06-18 19:23:14 +03:00
r1 = getreg(rs1); r2 = getreg(rs2)
ur1 = uint32(r1); ur2 = uint32(r2)
2026-06-18 14:01:36 +03:00
taken = 0
if(f3 == 0) taken = (r1 == r2)
else if(f3 == 1) taken = (r1 != r2)
else if(f3 == 4) taken = (r1 < r2)
else if(f3 == 5) taken = (r1 >= r2)
else if(f3 == 6) taken = (ur1 < ur2)
else if(f3 == 7) taken = (ur1 >= ur2)
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
2026-06-18 15:36:24 +03:00
pc += (taken ? immval : 4)
2026-06-18 14:01:36 +03:00
}
2026-06-18 19:23:14 +03:00
function amach_imm(opcode, f3, rd, rs1, immval, r1, ur1, shamt) {
r1 = getreg(rs1); ur1 = uint32(r1)
2026-06-18 14:01:36 +03:00
if(opcode == 3) { # load
2026-06-18 19:23:14 +03:00
if(f3 == 0 || f3 == 1 || f3 == 2 || f3 == 4 || f3 == 5) {
setreg(rd, read_mem(r1 + immval, 2^(f3 % 4), f3 < 4))
} else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
2026-06-18 14:01:36 +03:00
} else if(opcode == 19) { # 0x13, immediate arithmetic
shamt = bw_and(immval, 31)
if(f3 == 0) setreg(rd, r1 + immval)
else if(f3 == 4) setreg(rd, bw_xor(r1, immval))
else if(f3 == 6) setreg(rd, bw_or(r1, immval))
else if(f3 == 7) setreg(rd, bw_and(r1, immval))
else if(f3 == 1) setreg(rd, (r1 * (2^shamt)) % 4294967296) # slli
else if(f3 == 5 && immval < 1024) setreg(rd, int(ur1 / (2^shamt))) # srli
else if(f3 == 5 && immval >= 1024) setreg(rd, floor(r1 / (2^shamt))) # srai
else if(f3 == 2) setreg(rd, (r1 < immval) ? 1 : 0)
else if(f3 == 3) setreg(rd, (ur1 < immval) ? 1 : 0)
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
} else if(opcode == 103) { # 0x67, JALR
setreg(rd, pc)
pc = r1 + immval
} else if(opcode == 115) { # 0x73, system call / csr
if(immval == 0) handle_ecall(getreg(17), getreg(10), getreg(11), getreg(12), getreg(13), getreg(14), getreg(15))
2026-06-18 19:34:05 +03:00
else if(immval == 1) trapout(sprintf("EBREAK at 0x%X", pc-4))
2026-06-18 14:01:36 +03:00
else trapout(sprintf("Unimplemented external system call at 0x%X", pc-4))
} else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
# A-extension
function amach_atomic(f3, f5, aq, rl, rd, rs1, rs2, r1, r2, val, uval, ur2) {
2026-06-18 19:23:14 +03:00
r1 = getreg(rs1); r2 = getreg(rs2)
ur2 = uint32(r2)
2026-06-18 14:01:36 +03:00
if(f3 == 2) {
val = read_word(r1)
2026-06-18 19:23:14 +03:00
uval = uint32(val)
2026-06-18 14:01:36 +03:00
if(f5 == 0) { # amoadd.w
setreg(rd, val)
write_word(r1, val + r2)
} else if(f5 == 1) { # amoswap.w
setreg(rd, val)
write_word(r1, r2)
} else if(f5 == 2) { # lr.w
setreg(rd, val)
RES_ADDR = r1
} else if(f5 == 3) { # sc.w
if(RES_ADDR == r1) {
write_word(r1, r2)
setreg(rd, 0)
RES_ADDR = -1
2026-06-18 15:36:24 +03:00
} else setreg(rd, 1)
2026-06-18 14:01:36 +03:00
} else if(f5 == 4) { # amoxor.w
setreg(rd, val)
write_word(r1, bw_xor(val, r2))
} else if(f5 == 8) { # amoor.w
setreg(rd, val)
write_word(r1, bw_or(val, r2))
} else if(f5 == 12) { # amoand.w
setreg(rd, val)
write_word(r1, bw_and(val, r2))
} else if(f5 == 16) { # amomin.w
setreg(rd, val)
write_word(r1, val <= r2 ? val : r2)
} else if(f5 == 20) { # amomax.w
setreg(rd, val)
write_word(r1, val >= r2 ? val : r2)
} else if(f5 == 24) { # amominu.w
setreg(rd, val)
write_word(r1, uval <= ur2 ? val : r2)
} else if(f5 == 28) { # amomaxu.w
setreg(rd, val)
write_word(r1, uval >= ur2 ? val : r2)
} else trapout(sprintf("Unimplemented atomic instruction at 0x%X", pc-4))
} else trapout(sprintf("Illegal atomic instruction at 0x%X", pc-4))
}
2026-06-18 19:10:32 +03:00
# C-extension (compressed instructions) handler
function amach_comp(instr, op, f3, b12, r11_7, r9_7, r6_2, r4_2, shamt, imm, base, val) {
op = instr % 4
f3 = int(instr / 8192) % 8
b12 = int(instr / 4096) % 2
r11_7 = int(instr / 128) % 32
2026-06-18 19:16:21 +03:00
r9_7 = (r11_7 % 8) + 8
2026-06-18 19:10:32 +03:00
r6_2 = int(instr / 4) % 32
2026-06-18 19:16:21 +03:00
r4_2 = (r6_2 % 8) + 8
2026-06-18 19:10:32 +03:00
shamt = r6_2 + b12 * 32
2026-06-18 19:23:14 +03:00
if(op == 0) {
if(f3 == 0) { # C.ADDI4SPN
2026-06-18 19:16:21 +03:00
imm = (r11_7 % 16) * 64 + (int(r11_7 / 16) + b12 * 2) * 16 + (int(r6_2 / 8) % 2) * 8 + (int(r6_2 / 16) % 2) * 4
2026-06-18 19:23:14 +03:00
if(imm == 0) trapout(sprintf("Illegal instruction C.ADDI4SPN at 0x%X", pc-2))
2026-06-18 19:10:32 +03:00
setreg(r4_2, getreg(2) + imm)
2026-06-18 19:23:14 +03:00
} else if(f3 == 2) { # C.LW
2026-06-18 19:16:21 +03:00
imm = (int(r6_2 / 8) % 2) * 64 + (int(r11_7 / 8) % 4) * 8 + b12 * 32 + (int(r6_2 / 16) % 2) * 4
2026-06-18 19:23:14 +03:00
setreg(r4_2, read_mem(getreg(r9_7) + imm, 4, 1))
} else if(f3 == 6) { # C.SW
2026-06-18 19:16:21 +03:00
imm = (int(r6_2 / 8) % 2) * 64 + (int(r11_7 / 8) % 4) * 8 + b12 * 32 + (int(r6_2 / 16) % 2) * 4
2026-06-18 19:23:14 +03:00
write_mem(getreg(r9_7) + imm, getreg(r4_2), 4)
2026-06-18 19:10:32 +03:00
} else trapout(sprintf("Illegal instruction at 0x%X", pc-2))
2026-06-18 19:23:14 +03:00
} else if(op == 1) {
if(f3 == 0) { # C.NOP / C.ADDI
2026-06-18 19:10:32 +03:00
imm = r6_2 + b12 * 32
2026-06-18 19:23:14 +03:00
if(imm >= 32) imm -= 64
2026-06-18 19:10:32 +03:00
setreg(r11_7, getreg(r11_7) + imm)
2026-06-18 19:23:14 +03:00
} else if(f3 == 1 || f3 == 5) { # C.JAL / C.J
2026-06-18 19:16:21 +03:00
imm = b12 * 2048 + (int(r11_7 / 2) % 2) * 1024 + (int(r11_7 / 4) % 4) * 256 + (int(r6_2 / 16) % 2) * 128 + (r11_7 % 2) * 64 + (r6_2 % 2) * 32 + (int(r11_7 / 16)) * 16 + (int(r6_2 / 2) % 8) * 2
2026-06-18 19:23:14 +03:00
if(imm >= 2048) imm -= 4096
if(f3 == 1) setreg(1, pc)
2026-06-18 19:10:32 +03:00
pc = (pc - 2) + imm
2026-06-18 19:23:14 +03:00
} else if(f3 == 2) { # C.LI
2026-06-18 19:10:32 +03:00
imm = r6_2 + b12 * 32
2026-06-18 19:23:14 +03:00
if(imm >= 32) imm -= 64
2026-06-18 19:10:32 +03:00
setreg(r11_7, imm)
2026-06-18 19:23:14 +03:00
} else if(f3 == 3) { # C.ADDI16SP / C.LUI
if(r11_7 == 2) { # C.ADDI16SP
2026-06-18 19:16:21 +03:00
imm = b12 * 512 + (int(r6_2 / 2) % 4) * 128 + (int(r6_2 / 8) % 2) * 64 + (r6_2 % 2) * 32 + (int(r6_2 / 16) % 2) * 16
2026-06-18 19:23:14 +03:00
if(imm >= 512) imm -= 1024
if(imm == 0) trapout(sprintf("Illegal instruction C.ADDI16SP at 0x%X", pc-2))
2026-06-18 19:10:32 +03:00
setreg(2, getreg(2) + imm)
2026-06-18 19:23:14 +03:00
} else if(r11_7 != 0) { # C.LUI
2026-06-18 19:10:32 +03:00
imm = r6_2 + b12 * 32
2026-06-18 19:23:14 +03:00
if(imm >= 32) imm -= 64
2026-06-18 19:10:32 +03:00
imm = imm * 4096
2026-06-18 19:23:14 +03:00
if(imm == 0) trapout(sprintf("Illegal instruction C.LUI at 0x%X", pc-2))
2026-06-18 19:10:32 +03:00
setreg(r11_7, imm)
}
2026-06-18 19:23:14 +03:00
} else if(f3 == 4) {
2026-06-18 19:16:21 +03:00
val = int(r11_7 / 8) % 4
2026-06-18 19:23:14 +03:00
if(val == 0) { # C.SRLI
if(shamt >= 32) trapout(sprintf("Illegal shift amount %d at 0x%X", shamt, pc-2))
2026-06-18 19:10:32 +03:00
imm = getreg(r9_7)
2026-06-18 19:23:14 +03:00
if(imm < 0) imm += 4294967296
2026-06-18 19:10:32 +03:00
setreg(r9_7, int(imm / (2^shamt)))
2026-06-18 19:23:14 +03:00
} else if(val == 1) { # C.SRAI
if(shamt >= 32) trapout(sprintf("Illegal shift amount %d at 0x%X", shamt, pc-2))
2026-06-18 19:10:32 +03:00
setreg(r9_7, floor(getreg(r9_7) / (2^shamt)))
2026-06-18 19:23:14 +03:00
} else if(val == 2) { # C.ANDI
2026-06-18 19:10:32 +03:00
imm = r6_2 + b12 * 32
2026-06-18 19:23:14 +03:00
if(imm >= 32) imm -= 64
2026-06-18 19:10:32 +03:00
setreg(r9_7, bw_and(getreg(r9_7), imm))
2026-06-18 19:23:14 +03:00
} else if(val == 3) { # C.SUB, C.XOR, C.OR, C.AND
2026-06-18 19:16:21 +03:00
imm = int(r6_2 / 8) % 4
2026-06-18 19:23:14 +03:00
if(b12 != 0) trapout(sprintf("Illegal register-register instruction at 0x%X", pc-2))
if(imm == 0) setreg(r9_7, getreg(r9_7) - getreg(r4_2))
else if(imm == 1) setreg(r9_7, bw_xor(getreg(r9_7), getreg(r4_2)))
else if(imm == 2) setreg(r9_7, bw_or(getreg(r9_7), getreg(r4_2)))
else if(imm == 3) setreg(r9_7, bw_and(getreg(r9_7), getreg(r4_2)))
2026-06-18 19:10:32 +03:00
}
2026-06-18 19:23:14 +03:00
} else if(f3 == 6 || f3 == 7) { # C.BEQZ / C.BNEZ
2026-06-18 19:16:21 +03:00
imm = b12 * 256 + (int(r6_2 / 8) % 4) * 64 + (r6_2 % 2) * 32 + (int(r11_7 / 8) % 4) * 8 + (int(r6_2 / 2) % 4) * 2
2026-06-18 19:23:14 +03:00
if(imm >= 256) imm -= 512
2026-06-18 19:10:32 +03:00
val = (f3 == 6) ? (getreg(r9_7) == 0) : (getreg(r9_7) != 0)
pc = val ? (pc - 2) + imm : pc
}
2026-06-18 19:23:14 +03:00
} else if(op == 2) {
if(f3 == 0) { # C.SLLI
if(shamt >= 32) trapout(sprintf("Illegal shift amount %d at 0x%X", shamt, pc-2))
if(r11_7 != 0) {
2026-06-18 19:10:32 +03:00
setreg(r11_7, (getreg(r11_7) * (2^shamt)) % 4294967296)
}
2026-06-18 19:23:14 +03:00
} else if(f3 == 2) { # C.LWSP
if(r11_7 == 0) trapout(sprintf("Illegal instruction C.LWSP with rd=0 at 0x%X", pc-2))
2026-06-18 19:16:21 +03:00
imm = (r6_2 % 4) * 64 + b12 * 32 + (int(r6_2 / 4) % 8) * 4
2026-06-18 19:23:14 +03:00
setreg(r11_7, read_mem(getreg(2) + imm, 4, 1))
} else if(f3 == 4) { # C.JR, C.MV, C.JALR, C.ADD
if(b12 == 0) {
if(r6_2 == 0) { # C.JR
if(r11_7 == 0) trapout(sprintf("Illegal instruction C.JR with rs1=0 at 0x%X", pc-2))
2026-06-18 19:10:32 +03:00
pc = getreg(r11_7)
2026-06-18 19:23:14 +03:00
} else setreg(r11_7, getreg(r6_2)) # C.MV
2026-06-18 19:10:32 +03:00
} else {
2026-06-18 19:23:14 +03:00
if(r6_2 == 0) {
2026-06-18 19:34:05 +03:00
if(r11_7 == 0) trapout(sprintf("EBREAK at 0x%X", pc-2)) # C.EBREAK
else { # C.JALR
2026-06-18 19:10:32 +03:00
imm = getreg(r11_7)
setreg(1, pc)
pc = imm
}
2026-06-18 19:23:14 +03:00
} else setreg(r11_7, getreg(r11_7) + getreg(r6_2)) # C.ADD
2026-06-18 19:10:32 +03:00
}
2026-06-18 19:23:14 +03:00
} else if(f3 == 6) { # C.SWSP
2026-06-18 19:16:21 +03:00
imm = (r11_7 % 4) * 64 + int(r11_7 / 4) * 4 + b12 * 32
2026-06-18 19:23:14 +03:00
write_mem(getreg(2) + imm, getreg(r6_2), 4)
2026-06-18 19:10:32 +03:00
} else trapout(sprintf("Illegal instruction at 0x%X", pc-2))
} else trapout(sprintf("Illegal instruction at 0x%X", pc-2))
}
2026-06-18 14:01:36 +03:00
# main instruction decoding and execution routine
function amach_exec(instr, opcode, rd, rs1, rs2, imm, funct3, funct7) {
2026-06-18 19:23:14 +03:00
if((instr % 4) != 3) {
2026-06-18 19:10:32 +03:00
amach_comp(instr)
return
}
2026-06-18 14:01:36 +03:00
opcode = instr % 128
instr = int(instr / 128)
rd = instr % 32
imm = int(instr / 32)
funct3 = imm % 8
rs1 = int(imm / 8) % 32
rs2 = int(imm / 256) % 32
funct7 = int(imm / 8192)
if(opcode == 51) { # 0x33, register arithmetic, type R
amach_reg_arith(funct3, funct7, rd, rs1, rs2)
} else if(opcode == 47) { # 0x2f, atomic operations, type R
amach_atomic(funct3, int(funct7 / 4), int(funct7 / 2) % 2, funct7 % 2, rd, rs1, rs2)
} else if(opcode == 3 || opcode == 19 || opcode == 103 || opcode == 115) { # type I
amach_imm(opcode, funct3, rd, rs1, imm_sign_ex(rs2 + funct7 * 32))
} else if(opcode == 35) { # 0x23, store, type S
amach_store(funct3, rs1, rs2, imm_sign_ex(rd + funct7 * 32))
} else if(opcode == 99) { # 0x63, branch, type B
imm = rd + (funct7 % 64) * 32 + (rd % 2) * 2047 + int(funct7 / 64) * 4096
pc -= 4
amach_branch(funct3, rs1, rs2, imm_sign_ex_b(imm))
} else if(opcode == 111) { # 0x6F, JAL, type J
setreg(rd, pc)
imm_10_1 = int(rs2 / 2) + (funct7 % 64) * 16
imm_19_12 = funct3 + rs1 * 8
imm = imm_10_1 * 2 + (rs2 % 2) * 2048 + imm_19_12 * 4096 + int(funct7 / 64) * 1048576
if(imm >= 1048576) imm -= 2097152
pc += imm - 4
} else if(opcode == 55) { # 0x37, LUI, type U
setreg(rd, imm * 4096)
} else if(opcode == 23) { # 0x17, AUIPC, type U
setreg(rd, imm * 4096 + pc - 4)
} else if(opcode == 15) { # 0x0F, FENCE, type I / no-op
} else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
# Initialization section
BEGIN {
split("", MEM) # init the memory array
split("", REG) # init the (integer) registers
REG[0] = 0 # zero register
REG[2] = 2^31 # stack pointer top
RES_ADDR = -1 # reserved address for atomic ops
LVA = int(LVA)
pc = LVA
2026-06-18 19:54:49 +03:00
NEXT_FD = 3
2026-06-19 07:54:36 +03:00
LAST_SEC = -1
2026-06-18 19:54:49 +03:00
VIRT_NSEC = 0
srand()
2026-06-18 14:01:36 +03:00
}
# Decimal memory collection section
/^[[:space:]]*[[:digit:][:space:]]+/ {for(i=1; i<=NF; i++) MEM[pc++] = int($i)}
# Execution section
END {
2026-06-19 07:45:35 +03:00
# ELF detection and parsing
# The file has been loaded into MEM starting from original LVA
orig_LVA = LVA
elf_size = pc - orig_LVA
# Verify if it starts with ELF magic (0x7F, 'E', 'L', 'F') and is large enough to contain a header
if(elf_size >= 52 && MEM[LVA] == 127 && MEM[LVA+1] == 69 && MEM[LVA+2] == 76 && MEM[LVA+3] == 70) {
# Verify class is ELF32, data is LSB (little endian), and machine is RISC-V (243 / 0xf3)
if(MEM[LVA+4] != 1 || MEM[LVA+5] != 1 || (MEM[LVA+18] + MEM[LVA+19] * 256) != 243)
trapout("Incompatible ELF file (must be RV32 little-endian)")
# Read entry point (4 bytes at offset 24)
e_entry = MEM[LVA+24] + MEM[LVA+25] * 256 + MEM[LVA+26] * 65536 + MEM[LVA+27] * 16777216
# Read program header table offset (4 bytes at offset 28)
e_phoff = MEM[LVA+28] + MEM[LVA+29] * 256 + MEM[LVA+30] * 65536 + MEM[LVA+31] * 16777216
# Read number of program headers (2 bytes at offset 44)
e_phnum = MEM[LVA+44] + MEM[LVA+45] * 256
# Read size of program header entry (2 bytes at offset 42)
e_phentsize = MEM[LVA+42] + MEM[LVA+43] * 256
# Back up the entire ELF image to ELF_DATA array and clear MEM in the loaded range
for(i = 0; i < elf_size; i++) {
ELF_DATA[i] = MEM[orig_LVA + i]
delete MEM[orig_LVA + i]
}
# Load segment data into memory
for(p = 0; p < e_phnum; p++) {
ph_addr = e_phoff + p * e_phentsize
p_type = ELF_DATA[ph_addr] + ELF_DATA[ph_addr+1] * 256 + ELF_DATA[ph_addr+2] * 65536 + ELF_DATA[ph_addr+3] * 16777216
# We only load PT_LOAD (p_type == 1)
if(p_type == 1) {
p_offset = ELF_DATA[ph_addr+4] + ELF_DATA[ph_addr+5] * 256 + ELF_DATA[ph_addr+6] * 65536 + ELF_DATA[ph_addr+7] * 16777216
p_vaddr = ELF_DATA[ph_addr+8] + ELF_DATA[ph_addr+9] * 256 + ELF_DATA[ph_addr+10] * 65536 + ELF_DATA[ph_addr+11] * 16777216
p_filesz = ELF_DATA[ph_addr+16] + ELF_DATA[ph_addr+17] * 256 + ELF_DATA[ph_addr+18] * 65536 + ELF_DATA[ph_addr+19] * 16777216
p_memsz = ELF_DATA[ph_addr+20] + ELF_DATA[ph_addr+21] * 256 + ELF_DATA[ph_addr+22] * 65536 + ELF_DATA[ph_addr+23] * 16777216
# Copy p_filesz bytes from ELF_DATA to MEM
2026-06-19 07:54:36 +03:00
for(i = 0; i < p_filesz; i++) MEM[p_vaddr + i] = ELF_DATA[p_offset + i]
2026-06-19 07:45:35 +03:00
# Zero-initialize the remaining p_memsz - p_filesz bytes
2026-06-19 07:54:36 +03:00
for(i = p_filesz; i < p_memsz; i++) MEM[p_vaddr + i] = 0
2026-06-19 07:45:35 +03:00
}
}
# Override LVA to the entry point (actual memory start value inside the ELF file)
LVA = e_entry
}
2026-06-18 14:01:36 +03:00
memsize = pc - LVA
pc = LVA
while(pc > -1) {
instr = MEM[pc++] + 256 * MEM[pc++]
if((instr % 4) == 3) # full instruction
instr += 65536 * MEM[pc++] + 16777216 * MEM[pc++]
amach_exec(instr) # decode and execute
}
2026-06-18 20:10:23 +03:00
for(fd in FD_PATH) if(FD_DIRTY[fd]) write_fd_to_file(fd)
2026-06-18 14:01:36 +03:00
}