Kompilasi C –> RiscV –> Flipjump –> .fjm
Kompiler ini adalah bukti bahwa program apa pun dapat dikompilasi menjadi banyak NOT
operasi. Baca selengkapnya tentang FlipJump: Github, esolang.
Contoh program, primes/main.c:
int main() {
printf("Calculate primes up to: ");
int max_number;
scanf("%d", &max_number);
...
for (int p = 3; p <= max_number; p += 2) {
if (non_prime(p) == false) {
for (int i = p*p; i <= max_number; i += p) {
non_prime(i) = true;
}
printf("%dn", p);
}
}
return 0;
}
Dikompilasi menjadi ini:
Yang dikompilasi menjadi ini:
Yang kemudian dikompilasi menjadi:
Sekarang, jalankan (Ingat, ini adalah operasi flipjump yang sedang berjalan):
Calculate primes up to: 20
2
3
5
7
11
13
17
19
Program exited with exit code 0x0.
>>> pip install c2fj
>>> sudo apt install picolibc-riscv64-unknown-elf
Hanya python3 c2fj.py file.c
akan mengkompilasi file c Anda menjadi elf, menjadi file fj, menjadi fjm, lalu menjalankannya.
c2fj
mendukung tanda berikutnya:
--breakpoints
Tempatkan fj-breakpoint di awal alamat riscv yang ditentukan--single-step
Tempatkan fj-breakpoints di awal semua opcode riscv--unify_fj
Satukan file fj yang dihasilkan menjadi satu file--finish-after
Hentikan kompilasi pada langkah mana pun (sebelum menjalankan, sebelum membuat fjm, dll.)--build-dir
Simpan build di direktori ini
Kami mendukung penentuan a Makefile
path, bukan file c!
Makefile Anda harus bergantung pada beberapa konstanta itu c2fj
akan mengisi:
C2FJ_GCC_OPTIONS
C2FJ_LINKER_SCRIPT
C2FJ_SOURCES
C2FJ_INCLUDE_DIRS
ELF_OUT_PATH
Contoh Makefile:
GCC := riscv64-unknown-elf-gcc
GCC_FLAGS := -O3
SOURCES := $(C2FJ_SOURCES) main.c globals.c calculate_int.c
OBJECTS := $(SOURCES:.c=.o)
all: |
$(GCC) $(C2FJ_GCC_OPTIONS) $(GCC_FLAGS) $(SOURCES) -I $(C2FJ_INCLUDE_DIRS) -T $(C2FJ_LINKER_SCRIPT) -o $(ELF_OUT_PATH)
clean:
rm -r build 2>/dev/null || true
.PHONY: clean all
Anda juga dapat menentukan skrip linker Anda sendiri. Itu harus berisi yang berikut:
_stack_end
(tepat setelah akhir tumpukan)_sdata
(mulai dari bagian data)__heap_start
(mulai dari tumpukan)
Pertama, file C Anda dikompilasi ke peri RiscV.
Kompilasi dilakukan dengan picolibc, dan proyek menyediakan implementasi fungsi apa pun yang diperlukan, agar dapat mendukung fase kompilasi fj berikutnya.
Misalnya, lihat exit
(c2fj_init.c):
void exit(int status) {
asm volatile ("jal %0, .+10" ::"r"(status):"memory");
__builtin_unreachable();
}
Ia menggunakan jal dengan offset buruk, sehingga akan diuraikan di sini sebagai: (riscv_instructions.py)
elif imm == JAL_EXIT_IMMEDIATE:
return f' .syscall.exit {register_name(rd)}n'
Dengan demikian, kita akan sampai pada implementasi flipjump dari: (riscvlib.fj)
def exit src_register {
stl.output "Program exited with exit code "
hex.print_uint 2, src_register, 1, 1
stl.output ".n"
stl.loop
}
Anda dapat menganggapnya seperti ini: Kompilasi C->RiscV mengkompilasi syscall ke operasi RiscV khusus (tidak valid), yang diuraikan dan selanjutnya dikompilasi ke dalam implementasi fj dari “syscall yang diminta”. Syscall yang didukung dapat ditemukan di c2fj_init.c, dan berisi _getc
, _putc
, exit
, sbrk
.
Setiap opcode lainnya (Mari kita ikuti addi x10, x11, 7
misalnya), akan dikompilasi ke dalam dirinya sendiri.
Bagian RiscV -> FlipJump dari kompilasi mem-parsing elf yang dikompilasi, dan mencocokkan setiap opcode dengan makro flipjump yang sesuai. Misalnya:
elif opcode == RV_ALU_IMM:
if funct3 == RV_ADDI:
ops_file.write(i_type('addi', full_op))
Lalu riscv.addi
makro sedang digunakan. Makro operasi riscv dioptimalkan ruangnya. Mereka sangat dioptimalkan sehingga masing-masing membutuhkan 30-40
fj-ops di luar angkasa.
Itu memang disengaja. Pengoptimalan ruang memungkinkan proyek ini menangani basis kode c yang sangat besar, dan masih dapat mengkompilasinya tanpa masalah. Artinya, waktu kompilasi tidak terlalu bergantung pada ukuran basis kode Anda.
Cara kerjanya adalah setiap opcode diimplementasikan satu kali di dalam riscv.start
makro. Misalnya:
do_add:
hex.add .HLEN, .rs1, .rs2
stl.fret .ret
Perhatikan caranya addi
diimplementasikan:
def addi mov_from_rs1, mov_to_rs1, imm < .do_add {
.reg_imm_fast_op mov_from_rs1, mov_to_rs1, imm, .do_add
}
// Sets rs1 according to the given "fcall_label", rs2 to the given imm,
// fcalls "do_op", then moves the result to the appropriate dst reg.
def reg_imm_fast_op mov_from_dest, mov_to_rs1, imm, do_op @ table, xor_imm_to_rs2, end < .ret, .zero_rs2, .rs2 {
wflip .ret+w, table+dw, .ret
pad 16
table:
.ret+dbit+2; do_op // 4th
.ret+dbit+1; mov_to_rs1 // 1st
.ret+dbit+1; xor_imm_to_rs2 // 3rd
.ret+dbit+0; .zero_rs2 // 2nd
.ret+dbit+0; mov_from_dest // 5th
wflip .ret+w, table+5*dw, end // 6th
xor_imm_to_rs2:
.__xor_by_hex_const .HLEN, .rs2, imm
stl.fret .ret
end:
}
def moves_to_from_middle_regs {
zero_rs2:
hex.zero .HLEN, .rs2
stl.fret .ret
...
}
Sebagian besar ruang digunakan untuk keduanya wflip
s (jumlah @-4
ops).
Garis dengan 1st
dilakukan terlebih dahulu, 2nd
berada di urutan kedua, dan seterusnya. Itu cara kompak untuk melakukan banyak hal fcall
s dengan sepasang wflip
S.
Jadi seperti yang Anda lihat, makro mendapat a mov_to_rs1
Dan mov_from_dest
makro. Sebagai contoh dari addi x10, x11, 7
nama makro berikutnya akan ditentukan:
ns riscv {
mov_rs1_to_x10:
hex.mov .HLEN, .regs.x10, .rs1
stl.fret .ret
mov_x11_to_rs1:
hex.mov .HLEN, .rs1, .regs.x11
stl.fret .ret
}
Dan itu addi x10, x11, 7
opcode akan dikompilasi menjadi riscv.addi mov_rs1_to_x10, mov_x11_to_rs1, 7
.
Jadi ketika 1st
baris dieksekusi, itu mov_x11_to_rs1
kode akan dieksekusi, dan akan kembali ke awal 2nd
garis.
Perhatikan bahwa sebagian besar makro menggunakan variabel fj global rs1, rs2, rd
(bagian dari riscv
ruang nama).
Lalu, di baris kedua rs2
sedang dipusatkan.
Baris ketiga meng-xor nilai langsung yang diberikan (7
) ke rs2
dan baris keempat melakukan penambahan sebenarnya (dengan melompat ke do_op
yang do_add
dalam kasus kami).
Baris kelima akan memindahkan hasilnya (yang do_add
memasukkan rs1
) ke x10
menggunakan yang diberikan mov_rs1_to_x10
argumen.
Kemudian, makro akan selesai.
Jika Anda ingin memahaminya lebih baik, silakan melompat ke dalam FlipJump dan baca cara kerjanya di tingkat bit dan byte.
Fase selanjutnya menggunakan flipjump
paket python untuk mengkompilasi yang diberikan .fj
file ke dalam kompilasi .fjm
file (yang merupakan segmen data, dan yang saya maksud dengan data adalah sedikit membalik dan melompat).
Fase terakhir, menjalankan .fjm
file, menggunakan flipjump
paket untuk menafsirkan .fjm
file, dan memungkinkan untuk men-debugnya juga.
Pada bagian sebelumnya saya telah membahas tentang ops.fj
file yang dibuat dalam proses kompilasi, tetapi ada dua file lagi yang dibuat dalam proses itu juga.
Seluruh memori yang dapat dimuat dari elf yang dikompilasi sedang dimuat ke flipjump menggunakan file ini. Ini berisi semua byte memori yang dapat dimuat di fj hex
variabel.
Tidak ada batasan memori di dalamnya, sehingga program yang sedang berjalan dapat membaca/menulis/mengeksekusinya dengan bebas.
Perhatikan bahwa opcode riscv juga merupakan bagian dari memori yang dapat dimuat, dan Anda juga dapat memodifikasi bagian memori tersebut, dan itu akan berubah, tetapi opcode riscv yang dikompilasi itu sendiri (dalam ops.fj
) tidak akan berubah.
Itu adalah tabel lompat ke setiap alamat riscv yang dapat dijalankan. Itu membantu kami dalam melompati operasi, karena alamat makro dari operasi di ops.fj
tidak dapat diprediksi dengan mudah.
Pikirkan bagaimana Anda bisa melompat ke alamat 0x144. Labelnya riscv.ADDR_00000144
di dalam ops.fj
tidak berada di suatu tempat yang tetap, atau sesuatu yang berkaitan dengan 0x144
. Namun, saat ini; y menjalankan semut opcode untuk melompat ke alamat 0x144
. Lalu apa yang kita lakukan?
Gunakan meja lompat! Ini terlihat seperti:
segment .JMP + 0x00000000/4*dw
;.ADDR_00000000
;.ADDR_00000004
...
;.ADDR_00000144
Itu 0x144
alamat berada pada offset tetap dari global .JMP
alamat, sehingga melompat ke alamat memori riscv 0x144
menjadi semudah melompat ke alamat fj .JMP + 0x144*dw
(sebagai dw
adalah panjang satu opcode fj, dalam bit).
Jalankan saja pytest
untuk menjalankan tes. Paket ini diuji di linux dan python 3.13.
- bf2fj – kompiler Brainfuck ke FlipJump.
- FlipJump – Perakit makro bahasa flipjump, perpustakaan standar, dan juru bahasa.
- fji-cpp – Penerjemah C++ lebih cepat untuk FlipJump.