Situs web
PyPI - Versi

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:

img.png

Yang dikompilasi menjadi ini:

img.png

Yang kemudian dikompilasi menjadi:

img.png

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

Bagaimana jika proyek saya lebih dari satu c?

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 wflips (jumlah @-4 ops).
Garis dengan 1st dilakukan terlebih dahulu, 2nd berada di urutan kedua, dan seterusnya. Itu cara kompak untuk melakukan banyak hal fcalls dengan sepasang wflipS.

Jadi seperti yang Anda lihat, makro mendapat a mov_to_rs1 Dan mov_from_dest makro. Sebagai contoh dari addi x10, x11, 7nama 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 rs2dan 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 x10menggunakan 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.

Sumber

Krystian Wiśniewski
Krystian Wiśniewski is a dedicated Sports Reporter and Editor with a degree in Sports Journalism from He graduated with a degree in Journalism from the University of Warsaw. Bringing over 14 years of international reporting experience, Krystian has covered major sports events across Europe, Asia, and the United States of America. Known for his dynamic storytelling and in-depth analysis, he is passionate about capturing the excitement of sports for global audiences and currently leads sports coverage and editorial projects at Agen BRILink dan BRI.