Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,635 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module arm64
|
||||
|
||||
// ARM64 Instruction Encoding Helpers
|
||||
// These functions provide type-safe instruction encoding for the ARM64 backend.
|
||||
|
||||
// Register type for type safety
|
||||
type Reg = int
|
||||
|
||||
// Common register constants
|
||||
const xzr = Reg(31) // Zero register
|
||||
|
||||
const sp = Reg(31) // Stack pointer (context-dependent)
|
||||
|
||||
const fp = Reg(29) // Frame pointer
|
||||
|
||||
const lr = Reg(30) // Link register
|
||||
|
||||
// === Prologue/Epilogue ===
|
||||
|
||||
// stp fp, lr, [sp, -16]! (pre-indexed)
|
||||
fn asm_stp_fp_lr_pre() u32 {
|
||||
return 0xA9BF7BFD
|
||||
}
|
||||
|
||||
// mov fp, sp
|
||||
fn asm_mov_fp_sp() u32 {
|
||||
return 0x910003FD
|
||||
}
|
||||
|
||||
// ldp fp, lr, [sp], 16 (post-indexed)
|
||||
fn asm_ldp_fp_lr_post() u32 {
|
||||
return 0xA8C17BFD
|
||||
}
|
||||
|
||||
// ret
|
||||
fn asm_ret() u32 {
|
||||
return 0xD65F03C0
|
||||
}
|
||||
|
||||
// === Store/Load Pair ===
|
||||
|
||||
// stp r1, r2, [sp, -16]! (callee-saved pair)
|
||||
fn asm_stp_pair_pre(r1 Reg, r2 Reg) u32 {
|
||||
return 0xA9BF0000 | (u32(r2) << 10) | (31 << 5) | u32(r1)
|
||||
}
|
||||
|
||||
// ldp r1, r2, [sp], 16 (restore callee-saved pair)
|
||||
fn asm_ldp_pair_post(r1 Reg, r2 Reg) u32 {
|
||||
return 0xA8C10000 | (u32(r2) << 10) | (31 << 5) | u32(r1)
|
||||
}
|
||||
|
||||
// === Arithmetic ===
|
||||
|
||||
// add rd, rn, #imm12
|
||||
fn asm_add_imm(rd Reg, rn Reg, imm u32) u32 {
|
||||
return 0x91000000 | (imm << 10) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// add rd, rn, #imm, lsl #12
|
||||
fn asm_add_imm_lsl12(rd Reg, rn Reg, imm u32) u32 {
|
||||
return 0x91400000 | (imm << 10) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// add rd, rn, rm
|
||||
fn asm_add_reg(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x8B000000 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// add rd, rn, rm, lsl #3 (for GEP: index * 8)
|
||||
fn asm_add_reg_lsl3(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x8B000C00 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sub rd, rn, #imm12
|
||||
fn asm_sub_imm(rd Reg, rn Reg, imm u32) u32 {
|
||||
return 0xD1000000 | (imm << 10) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sub rd, rn, #imm12, lsl #12
|
||||
fn asm_sub_imm_lsl12(rd Reg, rn Reg, imm u32) u32 {
|
||||
return 0xD1400000 | (imm << 10) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sub rd, rn, rm
|
||||
fn asm_sub_reg(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0xCB000000 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// madd rd, rn, rm, xzr (multiply: rd = rn * rm)
|
||||
fn asm_mul(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x9B007C00 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sdiv rd, rn, rm
|
||||
fn asm_sdiv(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x9AC00C00 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// udiv rd, rn, rm
|
||||
fn asm_udiv(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x9AC00800 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// msub rd, rn, rm, ra (rd = ra - rn * rm)
|
||||
fn asm_msub(rd Reg, rn Reg, rm Reg, ra Reg) u32 {
|
||||
return 0x9B008000 | (u32(rm) << 16) | (u32(ra) << 10) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// === Logical ===
|
||||
|
||||
// and rd, rn, rm
|
||||
fn asm_and(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x8A000000 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// and rd, rn, #1 — truncate to 1 bit (AND Xd, Xn, #1: N=1, immr=0, imms=0x00)
|
||||
fn asm_and_imm_1(rd Reg, rn Reg) u32 {
|
||||
return 0x92400000 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// and rd, rn, #0xFF — zero-extend u8 (AND Xd, Xn, #0xFF: N=1, immr=0, imms=0x07)
|
||||
fn asm_and_imm_0xff(rd Reg, rn Reg) u32 {
|
||||
return 0x92401C00 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// and rd, rn, #0xFFFF — zero-extend u16 (AND Xd, Xn, #0xFFFF: N=1, immr=0, imms=0x0F)
|
||||
fn asm_and_imm_0xffff(rd Reg, rn Reg) u32 {
|
||||
return 0x92403C00 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// and rd, rn, #0xFFFFFFFF — zero-extend u32 (AND Xd, Xn, #0xFFFFFFFF: N=1, immr=0, imms=0x1F)
|
||||
fn asm_and_imm_0xffffffff(rd Reg, rn Reg) u32 {
|
||||
return 0x92407C00 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// orr rd, rn, rm
|
||||
fn asm_orr(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0xAA000000 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// eor rd, rn, rm (xor)
|
||||
fn asm_eor(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0xCA000000 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// orr rd, xzr, rm (mov rd, rm)
|
||||
fn asm_mov_reg(rd Reg, rm Reg) u32 {
|
||||
return 0xAA0003E0 | (u32(rm) << 16) | u32(rd)
|
||||
}
|
||||
|
||||
// === Shifts ===
|
||||
|
||||
// lslv rd, rn, rm (logical shift left variable)
|
||||
fn asm_lslv(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x9AC02000 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// asrv rd, rn, rm (arithmetic shift right variable)
|
||||
fn asm_asrv(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x9AC02800 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// lsrv rd, rn, rm (logical shift right variable)
|
||||
fn asm_lsrv(rd Reg, rn Reg, rm Reg) u32 {
|
||||
return 0x9AC02400 | (u32(rm) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// lsr xd, xn, #shift (logical shift right immediate, 64-bit)
|
||||
// Alias for UBFM Xd, Xn, #shift, #63
|
||||
fn asm_lsr_imm(rd Reg, rn Reg, shift u32) u32 {
|
||||
return 0xD340FC00 | ((shift & 0x3F) << 16) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// ubfx xd, xn, #0, #width (unsigned bitfield extract, zero-extend lower bits)
|
||||
// Alias for UBFM Xd, Xn, #0, #(width-1)
|
||||
fn asm_ubfx_lower(rd Reg, rn Reg, width u32) u32 {
|
||||
imms := (width - 1) & 0x3F
|
||||
return 0xD3400000 | (imms << 10) | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sxtw rd, rn — sign-extend 32-bit value to 64-bit (SBFM Xd, Xn, #0, #31)
|
||||
fn asm_sxtw(rd Reg, rn Reg) u32 {
|
||||
return 0x93407C00 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sxth rd, rn — sign-extend 16-bit value to 64-bit (SBFM Xd, Xn, #0, #15)
|
||||
fn asm_sxth(rd Reg, rn Reg) u32 {
|
||||
return 0x93403C00 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// sxtb rd, rn — sign-extend 8-bit value to 64-bit (SBFM Xd, Xn, #0, #7)
|
||||
fn asm_sxtb(rd Reg, rn Reg) u32 {
|
||||
return 0x93401C00 | (u32(rn) << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// === Compare ===
|
||||
|
||||
// cmp rn, rm (subs xzr, rn, rm) — 64-bit
|
||||
fn asm_cmp_reg(rn Reg, rm Reg) u32 {
|
||||
return 0xEB00001F | (u32(rm) << 16) | (u32(rn) << 5)
|
||||
}
|
||||
|
||||
// cmp xn, #imm12 (subs xzr, xn, #imm12) — 64-bit immediate compare
|
||||
fn asm_cmp_imm(rn Reg, imm12 u32) u32 {
|
||||
return 0xF100001F | (imm12 << 10) | (u32(rn) << 5)
|
||||
}
|
||||
|
||||
// cmp wn, #imm12 (subs wzr, wn, #imm12) — 32-bit immediate compare
|
||||
fn asm_cmp_imm_w(rn Reg, imm12 u32) u32 {
|
||||
return 0x7100001F | (imm12 << 10) | (u32(rn) << 5)
|
||||
}
|
||||
|
||||
// cmp wn, wm (subs wzr, wn, wm) — 32-bit, sign-aware for i32
|
||||
fn asm_cmp_reg_w(rn Reg, rm Reg) u32 {
|
||||
return 0x6B00001F | (u32(rm) << 16) | (u32(rn) << 5)
|
||||
}
|
||||
|
||||
// === Conditional Set ===
|
||||
|
||||
// cset rd, eq
|
||||
fn asm_cset_eq(rd Reg) u32 {
|
||||
return 0x9A9F17E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, ne
|
||||
fn asm_cset_ne(rd Reg) u32 {
|
||||
return 0x9A9F07E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, lt
|
||||
fn asm_cset_lt(rd Reg) u32 {
|
||||
return 0x9A9FA7E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, gt
|
||||
fn asm_cset_gt(rd Reg) u32 {
|
||||
return 0x9A9FD7E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, le
|
||||
fn asm_cset_le(rd Reg) u32 {
|
||||
return 0x9A9FC7E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, ge
|
||||
fn asm_cset_ge(rd Reg) u32 {
|
||||
return 0x9A9FB7E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, hi (unsigned greater than)
|
||||
fn asm_cset_hi(rd Reg) u32 {
|
||||
return 0x9A9F97E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, hs (unsigned greater or equal)
|
||||
fn asm_cset_hs(rd Reg) u32 {
|
||||
return 0x9A9F37E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, lo (unsigned less than)
|
||||
fn asm_cset_lo(rd Reg) u32 {
|
||||
return 0x9A9F27E0 | u32(rd)
|
||||
}
|
||||
|
||||
// cset rd, ls (unsigned less or equal)
|
||||
fn asm_cset_ls(rd Reg) u32 {
|
||||
return 0x9A9F87E0 | u32(rd)
|
||||
}
|
||||
|
||||
// === Float Compare ===
|
||||
|
||||
// fcmp dn, dm (compare two double-precision floats, sets NZCV)
|
||||
fn asm_fcmp_d(dn Reg, dm Reg) u32 {
|
||||
return 0x1E602000 | (u32(dm) << 16) | (u32(dn) << 5)
|
||||
}
|
||||
|
||||
// === Memory ===
|
||||
|
||||
// str rt, [rn] (store 64-bit)
|
||||
fn asm_str(rt Reg, rn Reg) u32 {
|
||||
return 0xF9000000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// str wt, [rn] (store 32-bit)
|
||||
fn asm_str_w(rt Reg, rn Reg) u32 {
|
||||
return 0xB9000000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// strh wt, [rn] (store 16-bit)
|
||||
fn asm_str_h(rt Reg, rn Reg) u32 {
|
||||
return 0x79000000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// strb wt, [rn] (store 8-bit)
|
||||
fn asm_str_b(rt Reg, rn Reg) u32 {
|
||||
return 0x39000000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// str rt, [rn, #imm12] (scaled by 8)
|
||||
// imm12 is the byte offset divided by 8; range 0..32760 step 8
|
||||
fn asm_str_imm(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0xF9000000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// str wt, [rn, #imm12] (32-bit store, unsigned offset scaled by 4)
|
||||
// imm12 is the byte offset divided by 4; range 0..16380 step 4
|
||||
fn asm_str_imm_w(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0xB9000000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// strh wt, [rn, #imm12] (16-bit store, unsigned offset scaled by 2)
|
||||
// imm12 is the byte offset divided by 2; range 0..8190 step 2
|
||||
fn asm_str_imm_h(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0x79000000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// strb wt, [rn, #imm12] (8-bit store, unsigned offset)
|
||||
// imm12 is the byte offset; range 0..4095
|
||||
fn asm_str_imm_b(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0x39000000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// stp rt, rt2, [rn, #simm7] (store pair, signed offset scaled by 8)
|
||||
// simm7 is byte offset / 8; range -512..504 step 8
|
||||
fn asm_stp_offset(rt Reg, rt2 Reg, rn Reg, simm7 i32) u32 {
|
||||
return 0xA9000000 | (u32(simm7 & 0x7F) << 15) | (u32(rt2) << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldp rt, rt2, [rn, #simm7] (load pair, signed offset scaled by 8)
|
||||
// simm7 is byte offset / 8; range -512..504 step 8
|
||||
fn asm_ldp_offset(rt Reg, rt2 Reg, rn Reg, simm7 i32) u32 {
|
||||
return 0xA9400000 | (u32(simm7 & 0x7F) << 15) | (u32(rt2) << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// stur xt, [xn, #simm9] (unscaled 64-bit store)
|
||||
fn asm_stur(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0xF8000000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// stur wt, [xn, #simm9] (unscaled 32-bit store)
|
||||
fn asm_stur_w(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0xB8000000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// sturh wt, [xn, #simm9] (unscaled 16-bit store)
|
||||
fn asm_stur_h(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0x78000000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// sturb wt, [xn, #simm9] (unscaled 8-bit store)
|
||||
fn asm_stur_b(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0x38000000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldr rt, [rn] (load 64-bit)
|
||||
fn asm_ldr(rt Reg, rn Reg) u32 {
|
||||
return 0xF9400000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldr wt, [rn] (load 32-bit, zero-extend to x)
|
||||
fn asm_ldr_w(rt Reg, rn Reg) u32 {
|
||||
return 0xB9400000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldrsw xt, [rn] (load 32-bit, sign-extend to 64-bit x)
|
||||
// Used for signed i32 loads to preserve negative values in 64-bit registers.
|
||||
fn asm_ldrsw(rt Reg, rn Reg) u32 {
|
||||
return 0xB9800000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldrh wt, [rn] (load 16-bit, zero-extend to x)
|
||||
fn asm_ldr_h(rt Reg, rn Reg) u32 {
|
||||
return 0x79400000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldrb wt, [rn] (load 8-bit, zero-extend to x)
|
||||
fn asm_ldr_b(rt Reg, rn Reg) u32 {
|
||||
return 0x39400000 | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldr rt, [rn, #imm12] (load 64-bit with unsigned scaled offset)
|
||||
// imm12 is the byte offset divided by 8; range 0..32760 step 8
|
||||
fn asm_ldr_imm(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0xF9400000 | ((imm12 & 0xFFF) << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldr wt, [rn, #imm12] (32-bit load, unsigned offset scaled by 4)
|
||||
// imm12 is the byte offset divided by 4; range 0..16380 step 4
|
||||
fn asm_ldr_imm_w(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0xB9400000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldrh wt, [rn, #imm12] (16-bit load, unsigned offset scaled by 2)
|
||||
// imm12 is the byte offset divided by 2; range 0..8190 step 2
|
||||
fn asm_ldr_imm_h(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0x79400000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldrb wt, [rn, #imm12] (8-bit load, unsigned offset)
|
||||
// imm12 is the byte offset; range 0..4095
|
||||
fn asm_ldr_imm_b(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0x39400000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldrsw xt, [rn, #imm12] (32-bit sign-extending load, unsigned offset scaled by 4)
|
||||
fn asm_ldrsw_imm(rt Reg, rn Reg, imm12 u32) u32 {
|
||||
return 0xB9800000 | (imm12 << 10) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldur xt, [xn, #simm9] (unscaled 64-bit load)
|
||||
fn asm_ldur(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0xF8400000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldur wt, [xn, #simm9] (unscaled 32-bit load, zero-extend)
|
||||
fn asm_ldur_w(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0xB8400000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldurh wt, [xn, #simm9] (unscaled 16-bit load, zero-extend)
|
||||
fn asm_ldur_h(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0x78400000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// ldurb wt, [xn, #simm9] (unscaled 8-bit load, zero-extend)
|
||||
fn asm_ldur_b(rt Reg, rn Reg, simm9 i32) u32 {
|
||||
return 0x38400000 | (u32(simm9 & 0x1FF) << 12) | (u32(rn) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// === Branches ===
|
||||
|
||||
// b #imm26 (unconditional branch)
|
||||
fn asm_b(rel26 i32) u32 {
|
||||
return 0x14000000 | (u32(rel26) & 0x3FFFFFF)
|
||||
}
|
||||
|
||||
// bl #imm26 (branch with link / call)
|
||||
fn asm_bl(rel26 i32) u32 {
|
||||
return 0x94000000 | (u32(rel26) & 0x3FFFFFF)
|
||||
}
|
||||
|
||||
// bl (placeholder for relocation)
|
||||
fn asm_bl_reloc() u32 {
|
||||
return 0x94000000
|
||||
}
|
||||
|
||||
// blr rn (branch with link to register)
|
||||
fn asm_blr(rn Reg) u32 {
|
||||
return 0xD63F0000 | (u32(rn) << 5)
|
||||
}
|
||||
|
||||
// cbnz rt, #imm19
|
||||
fn asm_cbnz(rt Reg, rel19 i32) u32 {
|
||||
return 0xB5000000 | ((u32(rel19) & 0x7FFFF) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// cbz rt, #imm19
|
||||
fn asm_cbz(rt Reg, rel19 i32) u32 {
|
||||
return 0xB4000000 | ((u32(rel19) & 0x7FFFF) << 5) | u32(rt)
|
||||
}
|
||||
|
||||
// b.cond #imm19 (conditional branch)
|
||||
fn asm_b_cond(cond u32, rel19 i32) u32 {
|
||||
return 0x54000000 | ((u32(rel19) & 0x7FFFF) << 5) | cond
|
||||
}
|
||||
|
||||
// Condition codes for b.cond
|
||||
const cond_eq = u32(0x0) // equal
|
||||
|
||||
const cond_ne = u32(0x1) // not equal
|
||||
|
||||
const cond_lt = u32(0xB) // less than (signed)
|
||||
|
||||
const cond_gt = u32(0xC) // greater than (signed)
|
||||
|
||||
const cond_le = u32(0xD) // less or equal (signed)
|
||||
|
||||
const cond_ge = u32(0xA) // greater or equal (signed)
|
||||
|
||||
// === Move Immediate ===
|
||||
|
||||
// movz rd, #imm16 (move wide with zero)
|
||||
fn asm_movz(rd Reg, imm16 u32) u32 {
|
||||
return 0xD2800000 | (imm16 << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// movk rd, #imm16, lsl #shift (move wide with keep)
|
||||
fn asm_movk(rd Reg, imm16 u32, shift int) u32 {
|
||||
hw := u32(shift / 16)
|
||||
return 0xF2800000 | (hw << 21) | (imm16 << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// movn rd, #imm16 (move wide with NOT)
|
||||
fn asm_movn(rd Reg, imm16 u32) u32 {
|
||||
return 0x92800000 | (imm16 << 5) | u32(rd)
|
||||
}
|
||||
|
||||
// === PC-Relative Addressing ===
|
||||
|
||||
// adrp rd, #imm21 (load page address - placeholder for reloc)
|
||||
fn asm_adrp(rd Reg) u32 {
|
||||
return 0x90000000 | u32(rd)
|
||||
}
|
||||
|
||||
// add rd, rd, #imm12 (add page offset - typically follows adrp)
|
||||
fn asm_add_pageoff(rd Reg) u32 {
|
||||
return 0x91000000 | u32(rd) | (u32(rd) << 5)
|
||||
}
|
||||
|
||||
// ldr rd, [rd, #imm12] (load from page offset - for GOT access, typically follows adrp)
|
||||
fn asm_ldr_pageoff(rd Reg) u32 {
|
||||
return 0xF9400000 | u32(rd) | (u32(rd) << 5)
|
||||
}
|
||||
|
||||
// === Stack Operations ===
|
||||
|
||||
// sub sp, sp, x10
|
||||
fn asm_sub_sp_reg(rm Reg) u32 {
|
||||
return 0xCB000000 | (u32(rm) << 16) | (31 << 5) | 31
|
||||
}
|
||||
|
||||
// add sp, sp, x10
|
||||
fn asm_add_sp_reg(rm Reg) u32 {
|
||||
return 0x8B000000 | (u32(rm) << 16) | (31 << 5) | 31
|
||||
}
|
||||
|
||||
// sub x10, x29, x10
|
||||
fn asm_sub_fp_to_reg(rd Reg, rm Reg) u32 {
|
||||
return 0xCB0003A0 | (u32(rm) << 16) | u32(rd)
|
||||
}
|
||||
|
||||
// === Float Operations ===
|
||||
|
||||
// fadd dd, dn, dm
|
||||
fn asm_fadd_d(dd int, dn int, dm int) u32 {
|
||||
return 0x1E602800 | (u32(dm) << 16) | (u32(dn) << 5) | u32(dd)
|
||||
}
|
||||
|
||||
// fadd d0, d0, d1 (common case)
|
||||
fn asm_fadd_d0_d0_d1() u32 {
|
||||
return 0x1E612800
|
||||
}
|
||||
|
||||
// fsub d0, d0, d1
|
||||
fn asm_fsub_d0_d0_d1() u32 {
|
||||
return 0x1E613800
|
||||
}
|
||||
|
||||
// fmul d0, d0, d1
|
||||
fn asm_fmul_d0_d0_d1() u32 {
|
||||
return 0x1E610800
|
||||
}
|
||||
|
||||
// fdiv d0, d0, d1
|
||||
fn asm_fdiv_d0_d0_d1() u32 {
|
||||
return 0x1E611800
|
||||
}
|
||||
|
||||
// fdiv d2, d0, d1
|
||||
fn asm_fdiv_d2_d0_d1() u32 {
|
||||
return 0x1E611802
|
||||
}
|
||||
|
||||
// frintz d2, d2 (truncate to integer)
|
||||
fn asm_frintz_d2() u32 {
|
||||
return 0x1E65C042
|
||||
}
|
||||
|
||||
// fnmsub d0, d2, d1, d0 (d0 = d0 - d2*d1)
|
||||
fn asm_fnmsub_d0_d2_d1_d0() u32 {
|
||||
return 0x1F618000
|
||||
}
|
||||
|
||||
// === Float Conversions ===
|
||||
|
||||
// fmov xd, dn (copy float bits to integer)
|
||||
fn asm_fmov_x_d(xd Reg, dn int) u32 {
|
||||
return 0x9E660000 | (u32(dn) << 5) | u32(xd)
|
||||
}
|
||||
|
||||
// fmov dd, xn (copy integer bits to float)
|
||||
fn asm_fmov_d_x(dd int, xn Reg) u32 {
|
||||
return 0x9E670000 | (u32(xn) << 5) | u32(dd)
|
||||
}
|
||||
|
||||
// fcvtzs xd, dn (float to signed int, truncate toward zero)
|
||||
fn asm_fcvtzs_x_d(xd Reg, dn int) u32 {
|
||||
return 0x9E780000 | (u32(dn) << 5) | u32(xd)
|
||||
}
|
||||
|
||||
// scvtf dd, xn (signed int to float)
|
||||
fn asm_scvtf_d_x(dd int, xn Reg) u32 {
|
||||
return 0x9E620000 | (u32(xn) << 5) | u32(dd)
|
||||
}
|
||||
|
||||
// ucvtf dd, xn (unsigned int to float)
|
||||
fn asm_ucvtf_d_x(dd int, xn Reg) u32 {
|
||||
return 0x9E630000 | (u32(xn) << 5) | u32(dd)
|
||||
}
|
||||
|
||||
// fcvtzu xd, dn (float to unsigned int, truncate toward zero)
|
||||
fn asm_fcvtzu_x_d(xd Reg, dn int) u32 {
|
||||
return 0x9E790000 | (u32(dn) << 5) | u32(xd)
|
||||
}
|
||||
|
||||
// fmov sd, wn (copy 32-bit integer to single-precision float register)
|
||||
fn asm_fmov_s_w(sd int, wn Reg) u32 {
|
||||
return 0x1E270000 | (u32(wn) << 5) | u32(sd)
|
||||
}
|
||||
|
||||
// fmov wd, sn (copy single-precision float to 32-bit integer register)
|
||||
fn asm_fmov_w_s(wd Reg, sn int) u32 {
|
||||
return 0x1E260000 | (u32(sn) << 5) | u32(wd)
|
||||
}
|
||||
|
||||
// fcvt dd, sn (convert single-precision to double-precision float)
|
||||
fn asm_fcvt_d_s(dd int, sn int) u32 {
|
||||
return 0x1E22C000 | (u32(sn) << 5) | u32(dd)
|
||||
}
|
||||
|
||||
// fcvt sd, dn (convert double-precision to single-precision float)
|
||||
fn asm_fcvt_s_d(sd int, dn int) u32 {
|
||||
return 0x1E624000 | (u32(dn) << 5) | u32(sd)
|
||||
}
|
||||
|
||||
// === Special ===
|
||||
|
||||
// udf #0 (undefined - trap)
|
||||
fn asm_udf() u32 {
|
||||
return 0x00000000
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,285 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module arm64
|
||||
|
||||
// import encoding.binary
|
||||
import os
|
||||
|
||||
// Mach-O Constants for ARM64
|
||||
const mh_magic_64 = u32(0xfeedfacf)
|
||||
const cpu_type_arm64 = 0x0100000c
|
||||
const cpu_subtype_arm64_all = 0
|
||||
|
||||
const lc_segment_64 = 0x19
|
||||
const lc_symtab = 0x2
|
||||
|
||||
const arm64_reloc_branch26 = 2
|
||||
const arm64_reloc_page21 = 3
|
||||
const arm64_reloc_pageoff12 = 4
|
||||
const arm64_reloc_got_load_page21 = 5
|
||||
const arm64_reloc_got_load_pageoff12 = 6
|
||||
|
||||
pub struct MachOObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
str_data []u8
|
||||
data_data []u8
|
||||
|
||||
relocs []RelocationInfo
|
||||
symbols []Symbol
|
||||
str_table []u8
|
||||
sym_by_name map[string]int // symbol name → index in symbols
|
||||
}
|
||||
|
||||
struct RelocationInfo {
|
||||
addr int
|
||||
sym_idx int
|
||||
pcrel bool
|
||||
length int
|
||||
extern bool
|
||||
type_ int
|
||||
}
|
||||
|
||||
struct Symbol {
|
||||
mut:
|
||||
name string
|
||||
type_ u8
|
||||
sect u8
|
||||
desc u16
|
||||
value u64
|
||||
name_off int
|
||||
}
|
||||
|
||||
pub fn MachOObject.new() &MachOObject {
|
||||
mut m := &MachOObject{
|
||||
str_table: [u8(0)]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_symbol(name string, addr u64, is_ext bool, sect u8) int {
|
||||
typ := if is_ext { u8(0x0f) } else { u8(0x0e) } // N_SECT | N_EXT : N_SECT
|
||||
|
||||
// Check if symbol already exists (e.g., was added as undefined earlier)
|
||||
if i := m.sym_by_name[name] {
|
||||
mut s := &m.symbols[i]
|
||||
s.type_ = typ
|
||||
s.sect = sect
|
||||
s.value = addr
|
||||
return i
|
||||
}
|
||||
|
||||
// Add new symbol
|
||||
idx := m.symbols.len
|
||||
name_off := m.str_table.len
|
||||
unsafe { m.str_table.push_many(name.str, name.len) }
|
||||
m.str_table << 0
|
||||
|
||||
m.symbols << Symbol{
|
||||
name: name
|
||||
type_: typ
|
||||
sect: sect
|
||||
desc: 0
|
||||
value: addr
|
||||
name_off: name_off
|
||||
}
|
||||
m.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_undefined(name string) int {
|
||||
// Check for any existing symbol with this name (defined or undefined)
|
||||
if i := m.sym_by_name[name] {
|
||||
return i
|
||||
}
|
||||
|
||||
idx := m.symbols.len
|
||||
name_off := m.str_table.len
|
||||
unsafe { m.str_table.push_many(name.str, name.len) }
|
||||
m.str_table << 0
|
||||
|
||||
m.symbols << Symbol{
|
||||
name: name
|
||||
type_: 0x01 // N_UNDF | N_EXT
|
||||
sect: 0
|
||||
desc: 0
|
||||
value: 0
|
||||
name_off: name_off
|
||||
}
|
||||
m.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_reloc(addr int, sym_idx int, typ int, pcrel bool) {
|
||||
m.relocs << RelocationInfo{
|
||||
addr: addr
|
||||
sym_idx: sym_idx // 0-based symbol table index
|
||||
type_: typ
|
||||
pcrel: pcrel
|
||||
length: 2
|
||||
extern: true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) write(path string) {
|
||||
mut buf := []u8{}
|
||||
|
||||
n_sects := 3
|
||||
header_size := 32
|
||||
seg_cmd_size := 72 + (80 * n_sects)
|
||||
symtab_cmd_size := 24
|
||||
load_cmds_size := seg_cmd_size + symtab_cmd_size
|
||||
|
||||
text_off := header_size + load_cmds_size
|
||||
text_len := m.text_data.len
|
||||
cstring_off := text_off + text_len
|
||||
cstring_len := m.str_data.len
|
||||
data_off := cstring_off + cstring_len
|
||||
data_len := m.data_data.len
|
||||
|
||||
reloc_off := data_off + data_len
|
||||
sym_off := reloc_off + (m.relocs.len * 8)
|
||||
sym_len := m.symbols.len * 16
|
||||
str_off := sym_off + sym_len
|
||||
str_size := m.str_table.len
|
||||
|
||||
// 1. Header
|
||||
write_u32_le(mut buf, mh_magic_64)
|
||||
write_u32_le(mut buf, u32(cpu_type_arm64))
|
||||
write_u32_le(mut buf, u32(cpu_subtype_arm64_all))
|
||||
write_u32_le(mut buf, 1) // MH_OBJECT
|
||||
write_u32_le(mut buf, 2) // ncmds
|
||||
write_u32_le(mut buf, u32(load_cmds_size))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
// 2. LC_SEGMENT_64
|
||||
write_u32_le(mut buf, u32(lc_segment_64))
|
||||
write_u32_le(mut buf, u32(seg_cmd_size))
|
||||
for _ in 0 .. 16 {
|
||||
buf << 0
|
||||
}
|
||||
write_u64_le(mut buf, 0)
|
||||
write_u64_le(mut buf, u64(text_len + cstring_len + data_len))
|
||||
write_u64_le(mut buf, u64(text_off))
|
||||
write_u64_le(mut buf, u64(text_len + cstring_len + data_len))
|
||||
write_u32_le(mut buf, 7)
|
||||
write_u32_le(mut buf, 7)
|
||||
write_u32_le(mut buf, u32(n_sects))
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
// Section 1: __text
|
||||
write_string_fixed(mut buf, '__text', 16)
|
||||
write_string_fixed(mut buf, '__TEXT', 16)
|
||||
write_u64_le(mut buf, 0) // Addr 0
|
||||
write_u64_le(mut buf, u64(text_len))
|
||||
write_u32_le(mut buf, u32(text_off))
|
||||
write_u32_le(mut buf, 4)
|
||||
write_u32_le(mut buf, u32(reloc_off))
|
||||
write_u32_le(mut buf, u32(m.relocs.len))
|
||||
write_u32_le(mut buf, u32(0x80000400))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
// Section 2: __cstring
|
||||
write_string_fixed(mut buf, '__cstring', 16)
|
||||
write_string_fixed(mut buf, '__TEXT', 16)
|
||||
write_u64_le(mut buf, u64(text_len)) // Addr = text_len
|
||||
write_u64_le(mut buf, u64(cstring_len))
|
||||
write_u32_le(mut buf, u32(cstring_off))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 2) // S_CSTRING_LITERALS
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
// Section 3: __data
|
||||
write_string_fixed(mut buf, '__data', 16)
|
||||
write_string_fixed(mut buf, '__DATA', 16)
|
||||
write_u64_le(mut buf, u64(text_len + cstring_len)) // Addr = text_len + cstring_len
|
||||
write_u64_le(mut buf, u64(data_len))
|
||||
write_u32_le(mut buf, u32(data_off))
|
||||
write_u32_le(mut buf, 3) // align 8
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
// 3. LC_SYMTAB
|
||||
write_u32_le(mut buf, u32(lc_symtab))
|
||||
write_u32_le(mut buf, u32(symtab_cmd_size))
|
||||
write_u32_le(mut buf, u32(sym_off))
|
||||
write_u32_le(mut buf, u32(m.symbols.len))
|
||||
write_u32_le(mut buf, u32(str_off))
|
||||
write_u32_le(mut buf, u32(str_size))
|
||||
|
||||
buf << m.text_data
|
||||
buf << m.str_data
|
||||
buf << m.data_data
|
||||
|
||||
for r in m.relocs {
|
||||
write_u32_le(mut buf, u32(r.addr))
|
||||
mut info := u32(r.sym_idx)
|
||||
if r.pcrel {
|
||||
info |= (1 << 24)
|
||||
}
|
||||
info |= (u32(r.length) << 25)
|
||||
if r.extern {
|
||||
info |= (1 << 27)
|
||||
}
|
||||
info |= (u32(r.type_) << 28)
|
||||
write_u32_le(mut buf, info)
|
||||
}
|
||||
|
||||
for s in m.symbols {
|
||||
write_u32_le(mut buf, u32(s.name_off))
|
||||
buf << s.type_
|
||||
buf << s.sect
|
||||
write_u16_le(mut buf, s.desc)
|
||||
write_u64_le(mut buf, s.value)
|
||||
}
|
||||
|
||||
buf << m.str_table
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn write_u32_le(mut b []u8, v u32) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
b << u8(v >> 16)
|
||||
b << u8(v >> 24)
|
||||
}
|
||||
|
||||
fn write_u64_le(mut b []u8, v u64) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
b << u8(v >> 16)
|
||||
b << u8(v >> 24)
|
||||
b << u8(v >> 32)
|
||||
b << u8(v >> 40)
|
||||
b << u8(v >> 48)
|
||||
b << u8(v >> 56)
|
||||
}
|
||||
|
||||
fn write_u16_le(mut b []u8, v u16) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
}
|
||||
|
||||
fn write_string_fixed(mut b []u8, s string, len int) {
|
||||
mut bytes := s.bytes()
|
||||
for bytes.len < len {
|
||||
bytes << 0
|
||||
}
|
||||
for i in 0 .. len {
|
||||
b << bytes[i]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import os
|
||||
|
||||
fn main() {
|
||||
println(os.args.len > 0)
|
||||
if os.args.len > 0 {
|
||||
println(os.args[0].len > 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
struct FnType {
|
||||
x int
|
||||
}
|
||||
|
||||
struct Void {}
|
||||
|
||||
type Type = FnType | Void
|
||||
|
||||
struct Fn {
|
||||
name string
|
||||
typ Type
|
||||
}
|
||||
|
||||
fn lookup(methods []&Fn, method_name string) bool {
|
||||
for method in methods {
|
||||
if method.name == method_name {
|
||||
return method.typ is FnType
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn main() {
|
||||
method := &Fn{
|
||||
name: 'write_string'
|
||||
typ: FnType{
|
||||
x: 1
|
||||
}
|
||||
}
|
||||
mut methods := []&Fn{}
|
||||
methods << method
|
||||
println(lookup(methods, 'write_string'))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fn main() {
|
||||
mut matrix := [][]int{len: 4}
|
||||
mut worklist := []int{}
|
||||
worklist << 2
|
||||
idx := worklist.pop()
|
||||
matrix[idx] << 10
|
||||
matrix[idx] << 5
|
||||
mut total := 0
|
||||
for value in matrix[2] {
|
||||
total += value
|
||||
}
|
||||
println(total)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
module main
|
||||
|
||||
struct Entry {
|
||||
name string
|
||||
initial_data []u8
|
||||
size int
|
||||
}
|
||||
|
||||
fn collect(entries []Entry) []u8 {
|
||||
mut out := []u8{}
|
||||
for entry in entries {
|
||||
if entry.initial_data.len > 0 {
|
||||
out << entry.initial_data
|
||||
continue
|
||||
}
|
||||
for _ in 0 .. entry.size {
|
||||
out << u8(0)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
entries := [
|
||||
Entry{
|
||||
name: 'a'
|
||||
initial_data: [u8(1), 2, 3]
|
||||
size: 0
|
||||
},
|
||||
Entry{
|
||||
name: 'b'
|
||||
initial_data: []u8{}
|
||||
size: 4
|
||||
},
|
||||
Entry{
|
||||
name: 'c'
|
||||
initial_data: [u8(9)]
|
||||
size: 0
|
||||
},
|
||||
]
|
||||
out := collect(entries)
|
||||
println(out.len)
|
||||
println(out[0])
|
||||
println(out[5])
|
||||
println(out[7])
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
type ValueID = int
|
||||
type TypeID = int
|
||||
|
||||
struct Value {
|
||||
id int
|
||||
typ TypeID
|
||||
index int
|
||||
}
|
||||
|
||||
struct Function {
|
||||
id int
|
||||
name string
|
||||
typ TypeID
|
||||
mut:
|
||||
blocks []int
|
||||
params []ValueID
|
||||
}
|
||||
|
||||
struct Module {
|
||||
mut:
|
||||
values []Value
|
||||
funcs []Function
|
||||
}
|
||||
|
||||
fn sum_param_types(mut m Module) int {
|
||||
mut out := 0
|
||||
params := m.funcs[0].params.clone()
|
||||
for i in [0, 1] {
|
||||
if i < params.len {
|
||||
param := params[i]
|
||||
out += int(m.values[param].typ)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut m := Module{}
|
||||
for i := 0; i < 200; i++ {
|
||||
m.values << Value{
|
||||
id: i
|
||||
typ: TypeID(i + 10)
|
||||
index: i
|
||||
}
|
||||
}
|
||||
m.funcs << Function{
|
||||
id: 0
|
||||
name: 'f'
|
||||
typ: TypeID(0)
|
||||
params: [ValueID(134), ValueID(135)]
|
||||
}
|
||||
println(sum_param_types(mut m))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
fn main() {
|
||||
pairs := [
|
||||
[1, 2]!,
|
||||
[2, 3]!,
|
||||
[1, 0]!,
|
||||
]
|
||||
mut got := []int{}
|
||||
for pair in pairs {
|
||||
got << pair[0]
|
||||
got << pair[1]
|
||||
}
|
||||
println(got)
|
||||
for i := 0; i < pairs.len; i++ {
|
||||
pair := pairs[i]
|
||||
println('${pair[0]},${pair[1]}')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module main
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,14 @@
|
||||
module main
|
||||
|
||||
struct DirLike {
|
||||
mut:
|
||||
d_name [256]u8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut d := DirLike{}
|
||||
d.d_name[255] = 7
|
||||
assert d.d_name[255] == 7
|
||||
assert d.d_name.len == 256
|
||||
println('ok')
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import v2.ast
|
||||
|
||||
fn main() {
|
||||
file := ast.File{
|
||||
name: @FILE
|
||||
mod: 'main'
|
||||
imports: [
|
||||
ast.ImportStmt{
|
||||
name: 'v2.pref'
|
||||
alias: 'pref'
|
||||
},
|
||||
]
|
||||
stmts: [
|
||||
ast.Stmt(ast.ImportStmt{
|
||||
name: 'v2.pref'
|
||||
alias: 'pref'
|
||||
}),
|
||||
]
|
||||
}
|
||||
flat := ast.flatten_files([file])
|
||||
file_node := flat.nodes[flat.files[0].file_id]
|
||||
imports := flat.file_cursor(0).imports().import_stmts()
|
||||
println(file_node.edge_count)
|
||||
println(imports.len)
|
||||
if imports.len > 0 {
|
||||
println(imports[0].name)
|
||||
println(imports[0].alias)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
module main
|
||||
|
||||
const page_size = 64
|
||||
|
||||
@[direct_array_access]
|
||||
fn fill_hash(data &u8, data_len int, out &u8) {
|
||||
for i in 0 .. 32 {
|
||||
unsafe {
|
||||
out[i] = if i < data_len { data[i] + 1 } else { u8(i) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_pages_shape(data &u8, mut hashes []u8, page_start int, page_end int, code_limit int) {
|
||||
mut hash_buf := [32]u8{}
|
||||
for page := page_start; page < page_end; page++ {
|
||||
start := page * page_size
|
||||
mut end := start + page_size
|
||||
if end > code_limit {
|
||||
end = code_limit
|
||||
}
|
||||
unsafe {
|
||||
fill_hash(data + start, end - start, &hash_buf[0])
|
||||
}
|
||||
hash_offset := page * 32
|
||||
for i in 0 .. 32 {
|
||||
hashes[hash_offset + i] = hash_buf[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut data := []u8{len: 150}
|
||||
for i in 0 .. data.len {
|
||||
data[i] = u8(i % 251)
|
||||
}
|
||||
mut hashes := []u8{len: 96}
|
||||
unsafe {
|
||||
hash_pages_shape(data.data, mut hashes, 0, 3, data.len)
|
||||
}
|
||||
println(hashes[0])
|
||||
println(hashes[32])
|
||||
println(hashes[64])
|
||||
println(hashes[95])
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
module main
|
||||
|
||||
struct GenShape {
|
||||
mut:
|
||||
cache []int
|
||||
stack []bool
|
||||
}
|
||||
|
||||
fn new_gen_shape(n int) &GenShape {
|
||||
return &GenShape{
|
||||
cache: []int{len: n}
|
||||
stack: []bool{len: n}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
g := new_gen_shape(256)
|
||||
mut sum := 0
|
||||
for i := 0; i < g.cache.len; i++ {
|
||||
sum += g.cache[i]
|
||||
}
|
||||
println(sum)
|
||||
println(g.cache.len)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
module main
|
||||
|
||||
fn sum_cache(n int) int {
|
||||
cache := []int{len: n}
|
||||
mut sum := 0
|
||||
for i := 0; i < cache.len; i++ {
|
||||
sum += cache[i]
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println(sum_cache(256))
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
println(2147483647)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
module main
|
||||
|
||||
struct TypeShape {
|
||||
kind int
|
||||
width int
|
||||
elem_type int
|
||||
len int
|
||||
fields []int
|
||||
field_names []string
|
||||
params []int
|
||||
ret_type int
|
||||
is_c_struct bool
|
||||
is_union bool
|
||||
is_unsigned bool
|
||||
}
|
||||
|
||||
fn fixed_array_size(types []TypeShape, typ_id int) int {
|
||||
typ := types[typ_id]
|
||||
if typ.kind == 4 {
|
||||
return typ.len * 4
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
types := [
|
||||
TypeShape{
|
||||
kind: 4
|
||||
elem_type: 1
|
||||
len: 128
|
||||
fields: []int{}
|
||||
params: []int{}
|
||||
},
|
||||
]
|
||||
println(fixed_array_size(types, 0))
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
module main
|
||||
|
||||
enum TypeKindShape {
|
||||
void_t
|
||||
int_t
|
||||
float_t
|
||||
ptr_t
|
||||
array_t
|
||||
struct_t
|
||||
func_t
|
||||
label_t
|
||||
metadata_t
|
||||
}
|
||||
|
||||
struct TypeShape {
|
||||
kind TypeKindShape
|
||||
width int
|
||||
elem_type int
|
||||
len int
|
||||
fields []int
|
||||
field_names []string
|
||||
params []int
|
||||
ret_type int
|
||||
is_c_struct bool
|
||||
is_union bool
|
||||
is_unsigned bool
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut types := []TypeShape{}
|
||||
for i := 0; i < 400; i++ {
|
||||
types << TypeShape{
|
||||
kind: .array_t
|
||||
elem_type: 1
|
||||
len: 64 + i
|
||||
}
|
||||
}
|
||||
println(types[0].len)
|
||||
println(types[63].len)
|
||||
println(types[128].len)
|
||||
println(types[399].len)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
module main
|
||||
|
||||
enum TypeKindShape {
|
||||
void_t
|
||||
int_t
|
||||
float_t
|
||||
ptr_t
|
||||
array_t
|
||||
struct_t
|
||||
func_t
|
||||
label_t
|
||||
metadata_t
|
||||
}
|
||||
|
||||
struct TypeShape {
|
||||
kind TypeKindShape
|
||||
width int
|
||||
elem_type int
|
||||
len int
|
||||
fields []int
|
||||
field_names []string
|
||||
params []int
|
||||
ret_type int
|
||||
is_c_struct bool
|
||||
is_union bool
|
||||
is_unsigned bool
|
||||
}
|
||||
|
||||
struct TypeStoreShape {
|
||||
mut:
|
||||
types []TypeShape
|
||||
cache map[string]int
|
||||
}
|
||||
|
||||
fn (mut ts TypeStoreShape) register(t TypeShape) int {
|
||||
id := ts.types.len
|
||||
ts.types << t
|
||||
return id
|
||||
}
|
||||
|
||||
fn (mut ts TypeStoreShape) get_array(elem int, length int) int {
|
||||
return ts.register(TypeShape{
|
||||
kind: .array_t
|
||||
elem_type: elem
|
||||
len: length
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut ts := TypeStoreShape{}
|
||||
ts.cache = map[string]int{}
|
||||
id := ts.get_array(1, 128)
|
||||
typ := ts.types[id]
|
||||
println(typ.len)
|
||||
println(typ.elem_type)
|
||||
println(int(typ.kind))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fn push_name(mut names []string, name string) {
|
||||
names << name
|
||||
println(names.len)
|
||||
}
|
||||
|
||||
fn wrap(mut names []string, name string) {
|
||||
push_name(mut names, name)
|
||||
println(names.len)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut names := []string{}
|
||||
wrap(mut names, 'abc')
|
||||
println(names.len)
|
||||
if names.len > 0 {
|
||||
println(names[0])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
mut lists := [][]int{len: 4}
|
||||
for i := 0; i < lists.len; i++ {
|
||||
lists[i] << (i + 10)
|
||||
lists[i] << (i + 20)
|
||||
}
|
||||
mut total := 0
|
||||
for i := 0; i < lists.len; i++ {
|
||||
total += lists[i].len
|
||||
total += lists[i][0]
|
||||
total += lists[i][1]
|
||||
}
|
||||
println(total)
|
||||
println(lists[3][0])
|
||||
println(lists[3][1])
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
module main
|
||||
|
||||
struct EmptyExpr {
|
||||
}
|
||||
|
||||
struct BasicLiteral {
|
||||
value string
|
||||
}
|
||||
|
||||
struct Ident {
|
||||
name string
|
||||
}
|
||||
|
||||
type Type = ArrayFixedType | ArrayType
|
||||
|
||||
type Expr = ArrayInitExpr | BasicLiteral | EmptyExpr | Ident | Type
|
||||
|
||||
struct ArrayType {
|
||||
elem_type Expr
|
||||
}
|
||||
|
||||
struct ArrayFixedType {
|
||||
len Expr
|
||||
elem_type Expr
|
||||
}
|
||||
|
||||
struct ArrayInitExpr {
|
||||
typ Expr
|
||||
exprs []Expr
|
||||
}
|
||||
|
||||
struct FieldDecl {
|
||||
name string
|
||||
value Expr
|
||||
}
|
||||
|
||||
fn parse_digits(s string) int {
|
||||
mut out := 0
|
||||
for i := 0; i < s.len; i++ {
|
||||
out = out * 10 + int(s[i] - `0`)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn fixed_len_from_type(typ Type) int {
|
||||
return match typ {
|
||||
ArrayFixedType {
|
||||
if typ.len is BasicLiteral {
|
||||
parse_digits(typ.len.value)
|
||||
} else {
|
||||
-2
|
||||
}
|
||||
}
|
||||
ArrayType {
|
||||
-3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_to_len(expr Expr) int {
|
||||
return match expr {
|
||||
Type {
|
||||
fixed_len_from_type(expr)
|
||||
}
|
||||
else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_len(field FieldDecl) int {
|
||||
if field.value is ArrayInitExpr {
|
||||
arr := field.value
|
||||
return type_to_len(arr.typ)
|
||||
}
|
||||
return -4
|
||||
}
|
||||
|
||||
fn main() {
|
||||
field := FieldDecl{
|
||||
name: 'buf'
|
||||
value: Expr(ArrayInitExpr{
|
||||
typ: Expr(Type(ArrayFixedType{
|
||||
len: Expr(BasicLiteral{
|
||||
value: '128'
|
||||
})
|
||||
elem_type: Expr(Ident{
|
||||
name: 'u8'
|
||||
})
|
||||
}))
|
||||
exprs: []Expr{}
|
||||
})
|
||||
}
|
||||
println(field_len(field))
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
module main
|
||||
|
||||
struct EmptyExpr {
|
||||
}
|
||||
|
||||
struct BasicLiteral {
|
||||
value int
|
||||
}
|
||||
|
||||
struct Ident {
|
||||
name string
|
||||
}
|
||||
|
||||
type Type = ArrayFixedType | ArrayType
|
||||
|
||||
type Expr = ArrayInitExpr | BasicLiteral | EmptyExpr | Ident | Type
|
||||
|
||||
struct ArrayType {
|
||||
elem_type Expr
|
||||
}
|
||||
|
||||
struct ArrayFixedType {
|
||||
len Expr
|
||||
elem_type Expr
|
||||
}
|
||||
|
||||
struct ArrayInitExpr {
|
||||
typ Expr
|
||||
exprs []Expr
|
||||
}
|
||||
|
||||
struct FieldDecl {
|
||||
name string
|
||||
value Expr
|
||||
}
|
||||
|
||||
fn fixed_len_from_type(typ Type) int {
|
||||
return match typ {
|
||||
ArrayFixedType {
|
||||
if typ.len is BasicLiteral {
|
||||
typ.len.value
|
||||
} else {
|
||||
-2
|
||||
}
|
||||
}
|
||||
ArrayType {
|
||||
-3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_to_len(expr Expr) int {
|
||||
return match expr {
|
||||
Type {
|
||||
fixed_len_from_type(expr)
|
||||
}
|
||||
else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_len(field FieldDecl) int {
|
||||
if field.value is ArrayInitExpr {
|
||||
arr := field.value
|
||||
return type_to_len(arr.typ)
|
||||
}
|
||||
return -4
|
||||
}
|
||||
|
||||
fn main() {
|
||||
field := FieldDecl{
|
||||
name: 'buf'
|
||||
value: Expr(ArrayInitExpr{
|
||||
typ: Expr(Type(ArrayFixedType{
|
||||
len: Expr(BasicLiteral{
|
||||
value: 128
|
||||
})
|
||||
elem_type: Expr(Ident{
|
||||
name: 'u8'
|
||||
})
|
||||
}))
|
||||
exprs: []Expr{}
|
||||
})
|
||||
}
|
||||
println(field_len(field))
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import v2.ast
|
||||
|
||||
struct Parameter {
|
||||
typ ast.Expr
|
||||
}
|
||||
|
||||
struct FnType {
|
||||
params []Parameter
|
||||
}
|
||||
|
||||
fn classify(expr ast.Expr) int {
|
||||
match expr {
|
||||
ast.Type {
|
||||
if expr is ast.ArrayType {
|
||||
return 11
|
||||
}
|
||||
return 7
|
||||
}
|
||||
else {
|
||||
return 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_param_types(fn_type FnType) int {
|
||||
mut out := 0
|
||||
idx := 0
|
||||
first := fn_type.params[idx]
|
||||
out += classify(first.typ)
|
||||
for i := 0; i < fn_type.params.len; i++ {
|
||||
param := fn_type.params[i]
|
||||
out += classify(param.typ)
|
||||
}
|
||||
for _, param in fn_type.params {
|
||||
out += classify(param.typ)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
typ := ast.Type(ast.ArrayType{
|
||||
elem_type: ast.Expr(ast.Ident{
|
||||
name: 'int'
|
||||
})
|
||||
})
|
||||
fn_type := FnType{
|
||||
params: [
|
||||
Parameter{
|
||||
typ: ast.Expr(typ)
|
||||
},
|
||||
]
|
||||
}
|
||||
println(classify_param_types(fn_type))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
assert 0o755 == 493
|
||||
assert 0b1010_0101 == 165
|
||||
println('ok')
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
module main
|
||||
|
||||
fn to_i64_direct(x u64) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn to_i64_if_expr(x u64, neg bool) i64 {
|
||||
return if neg { -i64(x) } else { i64(x) }
|
||||
}
|
||||
|
||||
fn to_i64_result_direct(x u64) !i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn to_i64_result_if_expr(x u64, neg bool) !i64 {
|
||||
return if neg { -i64(x) } else { i64(x) }
|
||||
}
|
||||
|
||||
fn to_i64_result_if_var(x u64, neg bool) !i64 {
|
||||
y := if neg { -i64(x) } else { i64(x) }
|
||||
return y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println(to_i64_direct(6))
|
||||
println(to_i64_if_expr(6, false))
|
||||
println(to_i64_result_direct(6) or { 99 })
|
||||
println(to_i64_result_if_expr(6, false) or { 99 })
|
||||
println(to_i64_result_if_var(6, false) or { 99 })
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
|
||||
struct Sample {
|
||||
a voidptr
|
||||
b int
|
||||
c bool
|
||||
}
|
||||
|
||||
fn mk(path string) !Sample {
|
||||
cfile := os.vfopen(path, 'wb')!
|
||||
fd := os.fileno(cfile)
|
||||
return Sample{
|
||||
a: cfile
|
||||
b: fd
|
||||
c: true
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
s := mk('/tmp/v2_result_postfix_unwrap.txt') or {
|
||||
println('err')
|
||||
return
|
||||
}
|
||||
println(s.b > 0)
|
||||
println(s.c)
|
||||
}
|
||||
|
||||
// v2-clobber-test
|
||||
@@ -0,0 +1,101 @@
|
||||
// Test runner for ARM64 backend tests
|
||||
// Run with: ./v run vlib/v2_toberemoved/gen/arm64/tests/run_tests.v
|
||||
// Or: cd vlib/v2_toberemoved/gen/arm64/tests && v run run_tests.v
|
||||
|
||||
module main
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
t0 := time.now()
|
||||
|
||||
vroot := os.dir(@VEXE)
|
||||
v2_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v')
|
||||
v2_binary := os.join_path(vroot, 'cmd', 'v2', 'v2')
|
||||
tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'arm64', 'tests')
|
||||
|
||||
// Build v2 compiler
|
||||
println('[*] Building v2...')
|
||||
build_res := os.execute('${@VEXE} ${v2_source} -o ${v2_binary}')
|
||||
if build_res.exit_code != 0 {
|
||||
eprintln('Error: Failed to build v2')
|
||||
eprintln(build_res.output)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Find all test files (*.v except run_tests.v)
|
||||
test_files := os.ls(tests_dir) or {
|
||||
eprintln('Error: Cannot list tests directory')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
mut passed := 0
|
||||
mut failed := 0
|
||||
mut test_count := 0
|
||||
|
||||
for file in test_files {
|
||||
if !file.ends_with('.v') || file == 'run_tests.v' {
|
||||
continue
|
||||
}
|
||||
test_count++
|
||||
test_path := os.join_path(tests_dir, file)
|
||||
test_name := file.replace('.v', '')
|
||||
|
||||
println('\n[*] Testing: ${file}')
|
||||
|
||||
// Run v2 with ARM64 backend
|
||||
v2_output := os.join_path(tests_dir, 'test_${test_name}')
|
||||
v2_cmd := '${v2_binary} -backend arm64 ${test_path} -o ${v2_output}'
|
||||
v2_res := os.execute(v2_cmd)
|
||||
if v2_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] v2 compilation failed')
|
||||
eprintln(v2_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Run the generated binary
|
||||
gen_res := os.execute(v2_output)
|
||||
if gen_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] Generated binary exited with code ${gen_res.exit_code}')
|
||||
eprintln(gen_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Run reference compiler
|
||||
ref_res := os.execute('${@VEXE} -n -w -enable-globals run ${test_path}')
|
||||
if ref_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] Reference compilation failed')
|
||||
eprintln(ref_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Compare outputs
|
||||
expected := ref_res.output.trim_space().replace('\r\n', '\n')
|
||||
actual := gen_res.output.trim_space().replace('\r\n', '\n')
|
||||
|
||||
if expected == actual {
|
||||
println(' [PASS] Output matches reference')
|
||||
passed++
|
||||
} else {
|
||||
println(' [FAIL] Output mismatch')
|
||||
println(' Expected:\n${expected}')
|
||||
println(' Got:\n${actual}')
|
||||
failed++
|
||||
}
|
||||
|
||||
// Clean up
|
||||
os.rm(v2_output) or {}
|
||||
}
|
||||
|
||||
println('\n========================================')
|
||||
println('Results: ${passed}/${test_count} tests passed')
|
||||
if failed > 0 {
|
||||
println('${failed} tests FAILED')
|
||||
exit(1)
|
||||
}
|
||||
println('Total time: ${time.since(t0)}')
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
println('script main')
|
||||
@@ -0,0 +1,35 @@
|
||||
struct IntInfo {
|
||||
value int
|
||||
}
|
||||
|
||||
struct StructInfo {
|
||||
name string
|
||||
value int
|
||||
}
|
||||
|
||||
type TypeInfo = IntInfo | StructInfo
|
||||
|
||||
fn name_contains_dot(name string) bool {
|
||||
return name.contains('.')
|
||||
}
|
||||
|
||||
fn has_dot(info TypeInfo) bool {
|
||||
mut cur := info
|
||||
if cur is StructInfo {
|
||||
return name_contains_dot(cur.name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn main() {
|
||||
with_dot := TypeInfo(StructInfo{
|
||||
name: 'foo.bar'
|
||||
value: 1
|
||||
})
|
||||
without_dot := TypeInfo(StructInfo{
|
||||
name: 'foobar'
|
||||
value: 2
|
||||
})
|
||||
println(has_dot(with_dot))
|
||||
println(has_dot(without_dot))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
assert '6'.int() == 6
|
||||
assert '10'.int() == 10
|
||||
assert '13'.int() == 13
|
||||
assert '-1'.int() == -1
|
||||
assert '10'.i32() == i32(10)
|
||||
println('32'.u64())
|
||||
println('32'.i64())
|
||||
println('-32'.i64())
|
||||
println('0x20'.u64())
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Tests for ARM64 backend large struct returns (> 16 bytes)
|
||||
// This tests the fix from commit 9919ddeb5 which handles:
|
||||
// - Saving/restoring x8 (indirect return pointer) for large struct returns
|
||||
// - Proper stack frame layout with callee-saved registers
|
||||
// - Large struct parameter copying
|
||||
// - String operations (string is 24 bytes: ptr + len + is_lit)
|
||||
|
||||
fn print_str(s string) {
|
||||
C.write(1, s.str, s.len)
|
||||
C.write(1, c'\n', 1)
|
||||
}
|
||||
|
||||
// ===================== FUNCTIONS RETURNING STRINGS =====================
|
||||
|
||||
// Test 1: Return a string literal (24 bytes indirect return)
|
||||
fn get_hello() string {
|
||||
return 'hello'
|
||||
}
|
||||
|
||||
// Test 2: Return concatenated string
|
||||
fn get_greeting(name string) string {
|
||||
return 'Hello, ' + name
|
||||
}
|
||||
|
||||
// Test 3: Nested string returns
|
||||
fn get_formal_greeting(name string) string {
|
||||
greeting := get_greeting(name)
|
||||
return greeting + '!'
|
||||
}
|
||||
|
||||
// Test 4: Multiple string concatenations
|
||||
fn concat_three(a string, b string, c string) string {
|
||||
return a + b + c
|
||||
}
|
||||
|
||||
// Test 5: String function calling another string function
|
||||
fn get_full_message() string {
|
||||
greeting := get_hello()
|
||||
suffix := ' world'
|
||||
return greeting + suffix
|
||||
}
|
||||
|
||||
// Test 6: Chain of string returns
|
||||
fn chain_a() string {
|
||||
return 'A'
|
||||
}
|
||||
|
||||
fn chain_b() string {
|
||||
return chain_a() + 'B'
|
||||
}
|
||||
|
||||
fn chain_c() string {
|
||||
return chain_b() + 'C'
|
||||
}
|
||||
|
||||
// Test 7: Conditional string return
|
||||
fn conditional_string(flag bool) string {
|
||||
if flag {
|
||||
return 'true_case'
|
||||
}
|
||||
return 'false_case'
|
||||
}
|
||||
|
||||
// ===================== MAIN =====================
|
||||
|
||||
fn main() {
|
||||
print_str('--- String Return Tests (Large Struct > 16 bytes) ---')
|
||||
|
||||
// Test 1: Basic string literal return
|
||||
s1 := get_hello()
|
||||
print_str(s1)
|
||||
|
||||
// Test 2: String concatenation return
|
||||
s2 := get_greeting('Alice')
|
||||
print_str(s2)
|
||||
|
||||
// Test 3: Nested string function calls
|
||||
s3 := get_formal_greeting('Bob')
|
||||
print_str(s3)
|
||||
|
||||
// Test 4: Multiple parameters concatenation
|
||||
s4 := concat_three('One', '-Two-', 'Three')
|
||||
print_str(s4)
|
||||
|
||||
// Test 5: Function calling function returning strings
|
||||
s5 := get_full_message()
|
||||
print_str(s5)
|
||||
|
||||
// Test 6: Chain of string returns
|
||||
s6 := chain_c()
|
||||
print_str(s6)
|
||||
|
||||
// Test 7: Conditional string returns
|
||||
s7a := conditional_string(true)
|
||||
s7b := conditional_string(false)
|
||||
print_str(s7a)
|
||||
print_str(s7b)
|
||||
|
||||
// Test 8: Direct string operations
|
||||
direct := 'Direct' + ' ' + 'Test'
|
||||
print_str(direct)
|
||||
|
||||
// Test 9: Interpolation with string return
|
||||
name := 'Claude'
|
||||
interp := 'Name: ${name}'
|
||||
print_str(interp)
|
||||
|
||||
// Test 10: Chained string concat expression
|
||||
chained := 'A' + 'B' + 'C' + 'D' + 'E'
|
||||
print_str(chained)
|
||||
|
||||
print_str('--- All tests completed ---')
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
module main
|
||||
|
||||
type EmptyExpr = u8
|
||||
|
||||
struct Node {
|
||||
value int
|
||||
}
|
||||
|
||||
type Expr = EmptyExpr | Node
|
||||
|
||||
const empty_expr = Expr(EmptyExpr(0))
|
||||
|
||||
fn main() {
|
||||
if empty_expr is EmptyExpr {
|
||||
println('ok')
|
||||
return
|
||||
}
|
||||
println('bad')
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
module main
|
||||
|
||||
type Type = ArrayFixed | Primitive
|
||||
|
||||
struct Primitive {
|
||||
size int
|
||||
}
|
||||
|
||||
struct ArrayFixed {
|
||||
len int
|
||||
elem_type Type
|
||||
}
|
||||
|
||||
struct ObjectCommon {
|
||||
name string
|
||||
typ Type
|
||||
}
|
||||
|
||||
struct Const {
|
||||
ObjectCommon
|
||||
}
|
||||
|
||||
struct Global {
|
||||
ObjectCommon
|
||||
}
|
||||
|
||||
type Object = Const | Global | Type
|
||||
|
||||
struct Scope {
|
||||
mut:
|
||||
objects map[string]Object
|
||||
}
|
||||
|
||||
fn (s &Scope) lookup(name string) ?Object {
|
||||
if obj := s.objects[name] {
|
||||
return obj
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (obj &Object) typ() Type {
|
||||
match obj {
|
||||
Const {
|
||||
return obj.typ
|
||||
}
|
||||
Global {
|
||||
return obj.typ
|
||||
}
|
||||
Type {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn array_len(t Type) int {
|
||||
return match t {
|
||||
ArrayFixed {
|
||||
t.len
|
||||
}
|
||||
Primitive {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut s := Scope{
|
||||
objects: map[string]Object{}
|
||||
}
|
||||
s.objects['buf'] = Object(Global{
|
||||
name: 'buf'
|
||||
typ: Type(ArrayFixed{
|
||||
len: 128
|
||||
elem_type: Type(Primitive{
|
||||
size: 8
|
||||
})
|
||||
})
|
||||
})
|
||||
if obj := s.lookup('buf') {
|
||||
println(array_len(obj.typ()))
|
||||
return
|
||||
}
|
||||
println(-2)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
module main
|
||||
|
||||
enum TypeKindShape {
|
||||
void_t
|
||||
int_t
|
||||
float_t
|
||||
ptr_t
|
||||
array_t
|
||||
struct_t
|
||||
func_t
|
||||
label_t
|
||||
metadata_t
|
||||
}
|
||||
|
||||
struct TypeShape {
|
||||
kind TypeKindShape
|
||||
width int
|
||||
elem_type int
|
||||
len int
|
||||
fields []int
|
||||
field_names []string
|
||||
params []int
|
||||
ret_type int
|
||||
is_c_struct bool
|
||||
is_union bool
|
||||
is_unsigned bool
|
||||
}
|
||||
|
||||
struct TypeStoreShape {
|
||||
mut:
|
||||
types []TypeShape
|
||||
cache map[string]int
|
||||
}
|
||||
|
||||
struct TargetShape {
|
||||
ptr_size int
|
||||
endian_little bool
|
||||
}
|
||||
|
||||
struct ModuleShape {
|
||||
mut:
|
||||
name string
|
||||
target TargetShape
|
||||
ssa_mod &int = unsafe { nil }
|
||||
env &int = unsafe { nil }
|
||||
type_store TypeStoreShape
|
||||
values []int
|
||||
instrs []int
|
||||
blocks []int
|
||||
funcs []int
|
||||
globals []int
|
||||
}
|
||||
|
||||
fn lower(src &ModuleShape) ModuleShape {
|
||||
return ModuleShape{
|
||||
name: src.name
|
||||
target: src.target
|
||||
ssa_mod: src.ssa_mod
|
||||
env: src.env
|
||||
type_store: src.type_store
|
||||
values: src.values.clone()
|
||||
instrs: src.instrs.clone()
|
||||
blocks: src.blocks.clone()
|
||||
funcs: src.funcs.clone()
|
||||
globals: src.globals.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn fixed_array_size(m &ModuleShape, typ_id int) int {
|
||||
typ := m.type_store.types[typ_id]
|
||||
if typ.kind == .array_t {
|
||||
return typ.len * 4
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut src := ModuleShape{}
|
||||
src.name = 'main'
|
||||
src.target = TargetShape{
|
||||
ptr_size: 8
|
||||
endian_little: true
|
||||
}
|
||||
src.type_store.cache = map[string]int{}
|
||||
src.type_store.types = [
|
||||
TypeShape{
|
||||
kind: .array_t
|
||||
elem_type: 1
|
||||
len: 128
|
||||
fields: []int{}
|
||||
params: []int{}
|
||||
},
|
||||
]
|
||||
src.values = [1, 2, 3]
|
||||
src.instrs = [4, 5]
|
||||
src.blocks = [6]
|
||||
src.funcs = [7]
|
||||
src.globals = [8]
|
||||
src.type_store.cache['x'] = 0
|
||||
dst := lower(&src)
|
||||
println(fixed_array_size(&dst, 0))
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
module main
|
||||
|
||||
enum TypeKindShape {
|
||||
void_t
|
||||
int_t
|
||||
float_t
|
||||
ptr_t
|
||||
array_t
|
||||
struct_t
|
||||
func_t
|
||||
label_t
|
||||
metadata_t
|
||||
}
|
||||
|
||||
struct TypeShape {
|
||||
kind TypeKindShape
|
||||
width int
|
||||
elem_type int
|
||||
len int
|
||||
fields []int
|
||||
field_names []string
|
||||
params []int
|
||||
ret_type int
|
||||
is_c_struct bool
|
||||
is_union bool
|
||||
is_unsigned bool
|
||||
}
|
||||
|
||||
struct TypeStoreShape {
|
||||
mut:
|
||||
types []TypeShape
|
||||
cache map[string]int
|
||||
}
|
||||
|
||||
struct ModuleShape {
|
||||
mut:
|
||||
type_store TypeStoreShape
|
||||
}
|
||||
|
||||
fn (mut ts TypeStoreShape) register(t TypeShape) int {
|
||||
id := ts.types.len
|
||||
ts.types << t
|
||||
return id
|
||||
}
|
||||
|
||||
fn (mut ts TypeStoreShape) get_array(elem int, length int) int {
|
||||
key := 'a${elem}_${length}'
|
||||
if id := ts.cache[key] {
|
||||
return id
|
||||
}
|
||||
id := ts.register(TypeShape{
|
||||
kind: .array_t
|
||||
elem_type: elem
|
||||
len: length
|
||||
})
|
||||
ts.cache[key] = id
|
||||
return id
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut ts := TypeStoreShape{
|
||||
cache: map[string]int{}
|
||||
}
|
||||
for i := 0; i < 400; i++ {
|
||||
ts.get_array(1, 64 + i)
|
||||
}
|
||||
src := ModuleShape{
|
||||
type_store: ts
|
||||
}
|
||||
dst := ModuleShape{
|
||||
type_store: src.type_store
|
||||
}
|
||||
println(dst.type_store.types[0].len)
|
||||
println(dst.type_store.types[63].len)
|
||||
println(dst.type_store.types[128].len)
|
||||
println(dst.type_store.types[399].len)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
module main
|
||||
|
||||
fn read_u32_le_like(data []u8, off int) u32 {
|
||||
return u32(data[off]) | (u32(data[off + 1]) << 8) | (u32(data[off + 2]) << 16) | (u32(data[
|
||||
off + 3]) << 24)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
instr := u32(0x910000a5)
|
||||
result := (instr & u32(0xffc003ff)) | (u32(0xa0) << 10)
|
||||
assert result == u32(0x910280a5)
|
||||
|
||||
sym_addr := u64(0x1000240e0)
|
||||
page_off := sym_addr & 0xfff
|
||||
result_from_sym := (instr & u32(0xffc003ff)) | (u32(page_off) << 10)
|
||||
assert result_from_sym == u32(0x910380a5)
|
||||
|
||||
bytes := [u8(0xa5), 0, 0, 0x91]
|
||||
instr_from_bytes := read_u32_le_like(bytes, 0)
|
||||
assert instr_from_bytes == u32(0x910000a5)
|
||||
result_from_bytes := (instr_from_bytes & u32(0xffc003ff)) | (u32(page_off) << 10)
|
||||
assert result_from_bytes == u32(0x910380a5)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,628 @@
|
||||
module main
|
||||
|
||||
struct Point {
|
||||
mut:
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
struct Rect {
|
||||
origin Point
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
enum Color {
|
||||
red
|
||||
green
|
||||
blue
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
up
|
||||
down
|
||||
left
|
||||
right
|
||||
}
|
||||
|
||||
fn add(a int, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn multiply(a int, b int) int {
|
||||
return a * b
|
||||
}
|
||||
|
||||
fn (p Point) sum() int {
|
||||
return p.x + p.y
|
||||
}
|
||||
|
||||
fn (p Point) scale(factor int) Point {
|
||||
return Point{
|
||||
x: p.x * factor
|
||||
y: p.y * factor
|
||||
}
|
||||
}
|
||||
|
||||
fn (r Rect) area() int {
|
||||
return r.width * r.height
|
||||
}
|
||||
|
||||
fn max(a int, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
fn abs(n int) int {
|
||||
if n < 0 {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
fn fibonacci(n int) int {
|
||||
if n <= 1 {
|
||||
return n
|
||||
}
|
||||
mut a := 0
|
||||
mut b := 1
|
||||
for i := 2; i <= n; i++ {
|
||||
c := a + b
|
||||
a = b
|
||||
b = c
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
fn color_name(c Color) string {
|
||||
if int(c) == 0 {
|
||||
return 'red'
|
||||
} else if int(c) == 1 {
|
||||
return 'green'
|
||||
} else if int(c) == 2 {
|
||||
return 'blue'
|
||||
}
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
fn collatz_steps(start int) int {
|
||||
mut n := start
|
||||
mut steps := 0
|
||||
for n > 1 {
|
||||
if n % 2 == 0 {
|
||||
n = n / 2
|
||||
} else {
|
||||
n = n * 3 + 1
|
||||
}
|
||||
steps = steps + 1
|
||||
}
|
||||
return steps
|
||||
}
|
||||
|
||||
fn sum_range(start int, end int) int {
|
||||
mut total := 0
|
||||
for i := start; i < end; i++ {
|
||||
total = total + i
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn is_even(n int) bool {
|
||||
return n % 2 == 0
|
||||
}
|
||||
|
||||
fn count_even(limit int) int {
|
||||
mut c := 0
|
||||
for i := 0; i < limit; i++ {
|
||||
if is_even(i) {
|
||||
c = c + 1
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
fn direction_dx(d Direction) int {
|
||||
if int(d) == 2 {
|
||||
return -1
|
||||
} else if int(d) == 3 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fn direction_dy(d Direction) int {
|
||||
if int(d) == 0 {
|
||||
return -1
|
||||
} else if int(d) == 1 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
__global g_val int
|
||||
|
||||
fn fib_recursive(n int) int {
|
||||
if n <= 1 {
|
||||
return n
|
||||
}
|
||||
return fib_recursive(n - 1) + fib_recursive(n - 2)
|
||||
}
|
||||
|
||||
fn factorial_recursive(n int) int {
|
||||
if n <= 1 {
|
||||
return 1
|
||||
}
|
||||
return n * factorial_recursive(n - 1)
|
||||
}
|
||||
|
||||
fn gcd(a int, b int) int {
|
||||
if b == 0 {
|
||||
return a
|
||||
}
|
||||
return gcd(b, a % b)
|
||||
}
|
||||
|
||||
fn power(base int, exp int) int {
|
||||
mut result := 1
|
||||
for i := 0; i < exp; i++ {
|
||||
result = result * base
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fn swap_point(mut p Point) {
|
||||
tmp := p.x
|
||||
p.x = p.y
|
||||
p.y = tmp
|
||||
}
|
||||
|
||||
fn scale_point(mut p Point, factor int) {
|
||||
p.x = p.x * factor
|
||||
p.y = p.y * factor
|
||||
}
|
||||
|
||||
fn nested_return(x int) int {
|
||||
if x < 10 {
|
||||
return 100
|
||||
} else {
|
||||
if x < 20 {
|
||||
return 200
|
||||
} else {
|
||||
return 300
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sum_many(a int, b int, c int, d int, e int, f int, g int, h int) int {
|
||||
return a + b + c + d + e + f + g + h
|
||||
}
|
||||
|
||||
fn clamp(x int, lo int, hi int) int {
|
||||
if x < lo {
|
||||
return lo
|
||||
} else if x > hi {
|
||||
return hi
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
fn print_str(s string) {
|
||||
C.puts(s.str)
|
||||
}
|
||||
|
||||
fn print_int(n int) {
|
||||
C.printf(c'%d\n', n)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// 1. Basic arithmetic
|
||||
x := 10
|
||||
y := 20
|
||||
z := add(x, y)
|
||||
|
||||
if z > 25 {
|
||||
print_str('big')
|
||||
} else {
|
||||
print_str('small')
|
||||
}
|
||||
|
||||
// 2. For loop with sum
|
||||
mut sum := 0
|
||||
for i := 0; i < 5; i++ {
|
||||
sum = sum + i
|
||||
}
|
||||
if sum == 10 {
|
||||
print_str('sum ok')
|
||||
}
|
||||
|
||||
// 3. Struct and method
|
||||
p := Point{
|
||||
x: 3
|
||||
y: 4
|
||||
}
|
||||
ps := p.sum()
|
||||
if ps == 7 {
|
||||
print_str('struct ok')
|
||||
}
|
||||
|
||||
// 4. While loop
|
||||
mut count := 3
|
||||
for count > 0 {
|
||||
count = count - 1
|
||||
}
|
||||
if count == 0 {
|
||||
print_str('loop ok2')
|
||||
}
|
||||
|
||||
// 5. Print the sum result
|
||||
print_int(z)
|
||||
|
||||
// 6. Nested function calls
|
||||
result := add(multiply(3, 4), multiply(5, 6))
|
||||
if result == 42 {
|
||||
print_str('nested ok')
|
||||
}
|
||||
|
||||
// 7. Multiple comparisons and boolean logic
|
||||
a := 15
|
||||
b := 25
|
||||
if a < b && b < 30 {
|
||||
print_str('logic ok')
|
||||
}
|
||||
|
||||
if a == 15 || b == 99 {
|
||||
print_str('or ok')
|
||||
}
|
||||
|
||||
// 8. Negation and abs
|
||||
neg := -42
|
||||
pos := abs(neg)
|
||||
if pos == 42 {
|
||||
print_str('abs ok')
|
||||
}
|
||||
|
||||
// 9. Nested if/else
|
||||
val := 50
|
||||
if val < 0 {
|
||||
print_str('negative')
|
||||
} else if val == 0 {
|
||||
print_str('zero')
|
||||
} else if val < 100 {
|
||||
print_str('medium')
|
||||
} else {
|
||||
print_str('large')
|
||||
}
|
||||
|
||||
// 10. Chained arithmetic
|
||||
chain := (10 + 20) * 3 - 5
|
||||
if chain == 85 {
|
||||
print_str('chain ok')
|
||||
}
|
||||
|
||||
// 11. Bitwise operations
|
||||
bw := 0xFF & 0x0F
|
||||
if bw == 15 {
|
||||
print_str('bit and ok')
|
||||
}
|
||||
|
||||
bw2 := 0xA0 | 0x05
|
||||
if bw2 == 0xA5 {
|
||||
print_str('bit or ok')
|
||||
}
|
||||
|
||||
bw3 := 1 << 4
|
||||
if bw3 == 16 {
|
||||
print_str('shift ok')
|
||||
}
|
||||
|
||||
// 12. Struct method returning struct
|
||||
p2 := Point{
|
||||
x: 2
|
||||
y: 3
|
||||
}
|
||||
p3 := p2.scale(10)
|
||||
if p3.x == 20 && p3.y == 30 {
|
||||
print_str('scale ok')
|
||||
}
|
||||
|
||||
// 13. Nested struct
|
||||
r := Rect{
|
||||
origin: Point{
|
||||
x: 1
|
||||
y: 2
|
||||
}
|
||||
width: 10
|
||||
height: 5
|
||||
}
|
||||
if r.area() == 50 {
|
||||
print_str('rect ok')
|
||||
}
|
||||
if r.origin.x == 1 && r.origin.y == 2 {
|
||||
print_str('nested struct ok')
|
||||
}
|
||||
|
||||
// 14. Fibonacci
|
||||
fib := fibonacci(10)
|
||||
if fib == 55 {
|
||||
print_str('fib ok')
|
||||
}
|
||||
|
||||
// 15. Max function
|
||||
m := max(33, 77)
|
||||
if m == 77 {
|
||||
print_str('max ok')
|
||||
}
|
||||
|
||||
// 16. Multiple mutable updates
|
||||
mut acc := 1
|
||||
for j := 1; j <= 5; j++ {
|
||||
acc = acc * j
|
||||
}
|
||||
if acc == 120 {
|
||||
print_str('factorial ok')
|
||||
}
|
||||
|
||||
// 17. Comparison operators
|
||||
if 10 >= 10 {
|
||||
print_str('ge ok')
|
||||
}
|
||||
if 9 <= 10 {
|
||||
print_str('le ok')
|
||||
}
|
||||
if 10 != 11 {
|
||||
print_str('ne ok')
|
||||
}
|
||||
|
||||
// 18. Modulo
|
||||
rem := 17 % 5
|
||||
if rem == 2 {
|
||||
print_str('mod ok')
|
||||
}
|
||||
|
||||
// 19. Mutable struct fields
|
||||
mut mp := Point{
|
||||
x: 10
|
||||
y: 20
|
||||
}
|
||||
mp.x = 100
|
||||
mp.y = mp.x + 50
|
||||
if mp.x == 100 && mp.y == 150 {
|
||||
print_str('mut struct ok')
|
||||
}
|
||||
|
||||
// 20. Division
|
||||
div := 100 / 3
|
||||
if div == 33 {
|
||||
print_str('div ok')
|
||||
}
|
||||
|
||||
// 21. Enum values
|
||||
c1 := Color.red
|
||||
c2 := Color.blue
|
||||
if int(c1) == 0 && int(c2) == 2 {
|
||||
print_str('enum ok')
|
||||
}
|
||||
|
||||
// 22. Enum in function
|
||||
name := color_name(Color.green)
|
||||
C.printf(c'color: %s\n', name.str)
|
||||
|
||||
// 23. Bool return value
|
||||
if is_even(42) {
|
||||
print_str('even ok')
|
||||
}
|
||||
if !is_even(7) {
|
||||
print_str('odd ok')
|
||||
}
|
||||
|
||||
// 24. Nested loops
|
||||
mut total := 0
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
total = total + 1
|
||||
}
|
||||
}
|
||||
if total == 12 {
|
||||
print_str('nested loops ok')
|
||||
}
|
||||
|
||||
// 25. Break in loop
|
||||
mut found := -1
|
||||
for i := 0; i < 100; i++ {
|
||||
if i * i > 50 {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == 8 {
|
||||
print_str('break ok')
|
||||
}
|
||||
|
||||
// 26. Continue in loop
|
||||
mut odd_sum := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
if is_even(i) {
|
||||
continue
|
||||
}
|
||||
odd_sum = odd_sum + i
|
||||
}
|
||||
if odd_sum == 25 {
|
||||
print_str('continue ok')
|
||||
}
|
||||
|
||||
// 27. Enum as direction
|
||||
dx := direction_dx(Direction.right)
|
||||
dy := direction_dy(Direction.down)
|
||||
if dx == 1 && dy == 1 {
|
||||
print_str('direction ok')
|
||||
}
|
||||
|
||||
// 28. Compound assignment operators
|
||||
mut ca := 10
|
||||
ca += 5
|
||||
ca -= 3
|
||||
ca *= 2
|
||||
if ca == 24 {
|
||||
print_str('compound ok')
|
||||
}
|
||||
|
||||
// 29. XOR
|
||||
xr := 0xFF ^ 0x0F
|
||||
if xr == 0xF0 {
|
||||
print_str('xor ok')
|
||||
}
|
||||
|
||||
// 30. Right shift
|
||||
rs := 256 >> 3
|
||||
if rs == 32 {
|
||||
print_str('rshift ok')
|
||||
}
|
||||
|
||||
// 31. Collatz conjecture (complex loop logic)
|
||||
steps := collatz_steps(27)
|
||||
if steps == 111 {
|
||||
print_str('collatz ok')
|
||||
}
|
||||
|
||||
// 32. count_even (function call in loop condition body)
|
||||
ev := count_even(10)
|
||||
if ev == 5 {
|
||||
print_str('count ok')
|
||||
}
|
||||
|
||||
// 33. Struct with zero fields
|
||||
origin := Point{}
|
||||
if origin.x == 0 && origin.y == 0 {
|
||||
print_str('zero struct ok')
|
||||
}
|
||||
|
||||
// 34. Multiple early returns
|
||||
m2 := max(max(10, 20), max(15, 5))
|
||||
if m2 == 20 {
|
||||
print_str('multi ret ok')
|
||||
}
|
||||
|
||||
// 35. sum_range
|
||||
sr := sum_range(1, 11)
|
||||
if sr == 55 {
|
||||
print_str('range sum ok')
|
||||
}
|
||||
|
||||
// 36. Recursive fibonacci
|
||||
rf := fib_recursive(10)
|
||||
if rf == 55 {
|
||||
print_str('rec fib ok')
|
||||
}
|
||||
|
||||
// 37. Recursive factorial
|
||||
fact := factorial_recursive(6)
|
||||
if fact == 720 {
|
||||
print_str('rec fact ok')
|
||||
}
|
||||
|
||||
// 38. GCD
|
||||
g := gcd(48, 18)
|
||||
if g == 6 {
|
||||
print_str('gcd ok')
|
||||
}
|
||||
|
||||
// 39. Power
|
||||
pw := power(2, 10)
|
||||
if pw == 1024 {
|
||||
print_str('power ok')
|
||||
}
|
||||
|
||||
// 40. Heap allocation (&Point{})
|
||||
hp := &Point{
|
||||
x: 42
|
||||
y: 84
|
||||
}
|
||||
if hp.x == 42 && hp.y == 84 {
|
||||
print_str('heap ok')
|
||||
}
|
||||
|
||||
// 41. Mut struct parameter (swap)
|
||||
mut sp := Point{
|
||||
x: 10
|
||||
y: 20
|
||||
}
|
||||
swap_point(mut sp)
|
||||
if sp.x == 20 && sp.y == 10 {
|
||||
print_str('swap ok')
|
||||
}
|
||||
|
||||
// 42. Mut struct parameter (scale)
|
||||
mut sc := Point{
|
||||
x: 5
|
||||
y: 3
|
||||
}
|
||||
scale_point(mut sc, 10)
|
||||
if sc.x == 50 && sc.y == 30 {
|
||||
print_str('mut param ok')
|
||||
}
|
||||
|
||||
// 43. Nested return (all branches return)
|
||||
nr1 := nested_return(5)
|
||||
nr2 := nested_return(15)
|
||||
nr3 := nested_return(25)
|
||||
if nr1 == 100 && nr2 == 200 && nr3 == 300 {
|
||||
print_str('nested ret ok')
|
||||
}
|
||||
|
||||
// 44. Global variable
|
||||
g_val = 100
|
||||
g_val += 50
|
||||
g_val -= 20
|
||||
if g_val == 130 {
|
||||
print_str('global ok')
|
||||
}
|
||||
|
||||
// 45. Many arguments (8 args)
|
||||
sm := sum_many(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
if sm == 36 {
|
||||
print_str('8 args ok')
|
||||
}
|
||||
|
||||
// 46. Infinite loop with break
|
||||
mut inf := 0
|
||||
for {
|
||||
inf++
|
||||
if inf == 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if inf == 10 {
|
||||
print_str('inf loop ok')
|
||||
}
|
||||
|
||||
// 47. Assert
|
||||
assert 2 + 2 == 4
|
||||
assert 10 > 5
|
||||
print_str('assert ok')
|
||||
|
||||
// 48. Double negation
|
||||
dn := !!true
|
||||
if dn {
|
||||
print_str('double neg ok')
|
||||
}
|
||||
|
||||
// 49. Clamp
|
||||
cl1 := clamp(5, 0, 10)
|
||||
cl2 := clamp(-5, 0, 10)
|
||||
cl3 := clamp(15, 0, 10)
|
||||
if cl1 == 5 && cl2 == 0 && cl3 == 10 {
|
||||
print_str('clamp ok')
|
||||
}
|
||||
|
||||
// 50. Heap struct method call
|
||||
hp2 := &Point{
|
||||
x: 7
|
||||
y: 8
|
||||
}
|
||||
hs := hp2.sum()
|
||||
if hs == 15 {
|
||||
print_str('heap method ok')
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,485 @@
|
||||
// vtest build: macos
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
import v2.parser
|
||||
import v2.pref as vpref
|
||||
import v2.token
|
||||
import v2.transformer
|
||||
import v2.types
|
||||
|
||||
fn generate_array_append_c_for_test(code string) string {
|
||||
return generate_array_append_c_for_test_files([code])
|
||||
}
|
||||
|
||||
fn generate_array_append_c_for_test_files(sources []string) string {
|
||||
tmp_dir := os.join_path(os.temp_dir(), 'v2_array_append_codegen_test_${os.getpid()}')
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
mut paths := []string{cap: sources.len}
|
||||
for i, code in sources {
|
||||
tmp_file := os.join_path(tmp_dir, 'file_${i}.v')
|
||||
os.write_file(tmp_file, code) or { panic('failed to write temp file') }
|
||||
paths << tmp_file
|
||||
}
|
||||
prefs := &vpref.Preferences{
|
||||
backend: .cleanc
|
||||
no_parallel: true
|
||||
}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut par := parser.Parser.new(prefs)
|
||||
files := par.parse_files(paths, mut file_set)
|
||||
env := types.Environment.new()
|
||||
mut checker := types.Checker.new(prefs, file_set, env)
|
||||
checker.check_files(files)
|
||||
mut trans := transformer.Transformer.new_with_pref(env, prefs)
|
||||
mut gen := Gen.new_with_env_and_pref(trans.transform_files(files), env, prefs)
|
||||
return gen.gen()
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_array_slice_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn main() {
|
||||
mut dst := []u8{}
|
||||
haystack := [u8(1), 2, 3]
|
||||
dst << haystack[1..3]
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){array__slice(haystack, 1, 3)}')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_array_literal_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn main() {
|
||||
mut dst := []u8{}
|
||||
dst << [u8(`$`)]
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){new_array_from_c_array(')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_local_array_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn source() []u8 {
|
||||
return [u8(1), 2]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut dst := []u8{}
|
||||
src := source()
|
||||
dst << src
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){src}')
|
||||
}
|
||||
|
||||
fn test_generate_c_push_many_to_mut_array_param_uses_param_pointer() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn extend(mut dst []usize, src []usize) {
|
||||
dst << src
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert csrc.contains('array__push_many(((array*)(dst)),')
|
||||
assert !csrc.contains('array__push_many((array*)&dst,')
|
||||
assert !csrc.contains('array__push_many(((array*)(&dst)),')
|
||||
}
|
||||
|
||||
fn test_generate_c_lowered_push_many_to_mut_array_param_uses_param_pointer() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct Index {
|
||||
entries map[string][]usize
|
||||
}
|
||||
|
||||
fn (idx Index) matches_into(candidate string, mut matches []usize) {
|
||||
if hits := idx.entries[candidate] {
|
||||
matches << hits
|
||||
}
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert csrc.contains('array__push_many(((array*)(matches)),')
|
||||
assert !csrc.contains('array__push_many((array*)&matches,')
|
||||
assert !csrc.contains('array__push_many(((array*)(&matches)),')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_fn_pointer_array_return_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn source(data []u8) []u8 {
|
||||
return data
|
||||
}
|
||||
|
||||
fn consume(hash_func fn ([]u8) []u8) {
|
||||
mut dst := []u8{}
|
||||
src := hash_func(dst)
|
||||
dst << src
|
||||
}
|
||||
|
||||
fn main() {
|
||||
consume(source)
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){src}')
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_bitwise_or_inside_array_append_value() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn main() {
|
||||
mut bytes := []u8{}
|
||||
n1 := u8(1)
|
||||
n0 := u8(2)
|
||||
bytes << (n1 << 4) | n0
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push(')
|
||||
assert csrc.contains('| n0')
|
||||
assert !csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('_arr_push_many_tmp_')
|
||||
assert !csrc.contains('}) | n0')
|
||||
}
|
||||
|
||||
fn test_generate_c_wraps_mut_u8_array_param_bitwise_append_value() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn write_int(mut out []u8, value u64, high_bits u8) {
|
||||
out << high_bits | u8(value)
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push(((array*)(out)), &(u8[1]){')
|
||||
assert !csrc.contains('array__push(((array*)(out)), (high_bits | ((u8)(value))))')
|
||||
}
|
||||
|
||||
fn test_generate_c_appends_for_mut_pointer_value_to_array() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct PullRequest {
|
||||
mut:
|
||||
repo_name string
|
||||
}
|
||||
|
||||
fn copy(mut prs []PullRequest) []PullRequest {
|
||||
mut out := []PullRequest{}
|
||||
for mut pr in prs {
|
||||
pr.repo_name = "x"
|
||||
out << pr
|
||||
}
|
||||
return out
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push(((array*)(&out)), pr);')
|
||||
assert !csrc.contains('array__push(((array*)(&out)), &(PullRequest[1]){pr});')
|
||||
}
|
||||
|
||||
fn test_generate_c_indexes_local_array_that_shadows_function_name() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn bytes() []u8 {
|
||||
return []u8{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut bytes := [u8(1)]
|
||||
bytes[0] &= u8(3)
|
||||
}
|
||||
')
|
||||
assert csrc.contains('((u8*)bytes.data)[((int)(0))] &= ((u8)(3));')
|
||||
assert !csrc.contains('bytes &= ((u8)(3));')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_typed_zero_for_array_tuple_if_declarations() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn pick(operand_a []u64, operand_b []u64) {
|
||||
mut a, mut b := if operand_a.len >= operand_b.len {
|
||||
operand_a, operand_b
|
||||
} else {
|
||||
operand_b, operand_a
|
||||
}
|
||||
_ = a
|
||||
_ = b
|
||||
}
|
||||
')
|
||||
assert csrc.contains('Array_u64 a = ((Array_u64){0});')
|
||||
assert csrc.contains('Array_u64 b = ((Array_u64){0});')
|
||||
assert !csrc.contains('Array_u64 a = 0;')
|
||||
assert !csrc.contains('Array_u64 b = 0;')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_default_init_for_empty_array_struct_fields() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct Pool {
|
||||
items []voidptr
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pool := Pool{
|
||||
items: []
|
||||
}
|
||||
_ = pool.items.len
|
||||
}
|
||||
')
|
||||
assert csrc.contains('.items = __new_array_with_default_noscan(0, 0, sizeof(void*)')
|
||||
assert !csrc.contains('.items = new_array_from_c_array(0, 0, sizeof(void*)')
|
||||
}
|
||||
|
||||
fn test_generate_c_specializes_generic_pointer_array_push_literal() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct Foo {}
|
||||
|
||||
fn get_results_ref[T](items []voidptr) []&T {
|
||||
mut res := []&T{cap: items.len}
|
||||
for i in 0 .. items.len {
|
||||
res << unsafe { &T(items[i]) }
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
fn main() {
|
||||
items := []voidptr{}
|
||||
res := get_results_ref[Foo](items)
|
||||
_ = res.len
|
||||
}
|
||||
')
|
||||
assert csrc.contains('(Foo*[1]){((Foo*)')
|
||||
assert !csrc.contains('(T*[1])')
|
||||
assert !csrc.contains('stdatomic__T')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_captured_mut_array_pointer_for_closure_push() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn consume(cb fn (int) bool) {
|
||||
_ = cb(1)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut values := []int{}
|
||||
consume(fn [mut values] (value int) bool {
|
||||
values << value
|
||||
return true
|
||||
})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('Array_int* values = _anon_fn_')
|
||||
assert csrc.contains('array__push(((array*)(values)),')
|
||||
assert !csrc.contains('array__push(((array*)(&values)),')
|
||||
}
|
||||
|
||||
fn test_generate_c_updates_captured_mut_scalar_for_closure_postfix() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn consume(cb fn () bool) {
|
||||
_ = cb()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut count := u64(0)
|
||||
consume(fn [mut count] () bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('u64* count = _anon_fn_')
|
||||
assert csrc.contains('(*count)++')
|
||||
assert !csrc.contains('count++;')
|
||||
}
|
||||
|
||||
fn test_generate_c_evaluates_generic_comptime_is_against_implements() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
interface Writer {
|
||||
write([]u8) !int
|
||||
}
|
||||
|
||||
struct Buffer implements Writer {}
|
||||
|
||||
fn (mut b Buffer) write(buf []u8) !int {
|
||||
_ = b
|
||||
return buf.len
|
||||
}
|
||||
|
||||
fn choose[T]() int {
|
||||
$if T is Writer {
|
||||
return 1
|
||||
} $else {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = choose[Buffer]()
|
||||
}
|
||||
')
|
||||
assert csrc.contains('return 1;')
|
||||
assert !csrc.contains('return 2;')
|
||||
}
|
||||
|
||||
fn test_generate_c_specializes_comptime_pointer_field_generic_call_by_field_type() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn struct_field_should_encode[T](val T) bool {
|
||||
_ = val
|
||||
return true
|
||||
}
|
||||
|
||||
fn encode_fields[T](val T) bool {
|
||||
mut ok := true
|
||||
$for field in T.fields {
|
||||
ok = struct_field_should_encode(val.$(field.name))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
struct Node {
|
||||
next &Node = unsafe { nil }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = encode_fields(Node{})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('struct_field_should_encode_T_Nodeptr(val.next)')
|
||||
assert !csrc.contains('struct_field_should_encode_T_Node((*val.next))')
|
||||
}
|
||||
|
||||
fn test_generate_c_specializes_comptime_generic_pointer_field_by_field_type() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
module json2
|
||||
|
||||
struct ValueInfo {}
|
||||
|
||||
struct Node[T] {
|
||||
value T
|
||||
next &Node[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
struct Decoder {
|
||||
current_node &Node[ValueInfo] = unsafe { nil }
|
||||
}
|
||||
|
||||
struct EncoderFieldInfo {}
|
||||
|
||||
struct Encoder {}
|
||||
|
||||
fn (mut encoder Encoder) encode_value[T](val T) {
|
||||
$if T is $pointer {
|
||||
encoder.encode_value(*val)
|
||||
} $else $if T is $struct {
|
||||
_ = encoder.encode_fields[T](val)
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo {
|
||||
return []EncoderFieldInfo{}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_struct_field_value[T](val T) {
|
||||
$if T.indirections == 1 {
|
||||
encoder.encode_value(*val)
|
||||
} $else {
|
||||
encoder.encode_value(val)
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_field_should_encode[T](field_info EncoderFieldInfo, val T) bool {
|
||||
_ = field_info
|
||||
_ = val
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_fields[T](val T) bool {
|
||||
field_infos := encoder.cached_field_infos[T]()
|
||||
mut ok := true
|
||||
mut i := 0
|
||||
$for field in T.fields {
|
||||
field_info := field_infos[i]
|
||||
ok = struct_field_should_encode(field_info, val.$(field.name))
|
||||
encoder.encode_struct_field_value(val.$(field.name))
|
||||
i++
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
fn use_encoder() {
|
||||
mut encoder := Encoder{}
|
||||
_ = encoder.encode_fields(Decoder{})
|
||||
_ = encoder.encode_fields(Node[ValueInfo]{})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('json2__struct_field_should_encode_T_json2_Nodeptr(field_info, val.current_node)')
|
||||
assert csrc.contains('json2__Encoder__encode_struct_field_value_T_json2_Nodeptr(encoder, val.current_node)')
|
||||
assert csrc.contains('json2__Encoder__cached_field_infos_T_json2_Decoder(encoder)')
|
||||
assert csrc.contains('Array_json2__EncoderFieldInfo json2__Encoder__cached_field_infos_T_json2_Node(json2__Encoder* encoder)')
|
||||
assert !csrc.contains('json2__struct_field_should_encode_T_json2_Node(field_info, (*val.current_node))')
|
||||
assert !csrc.contains('json2__Encoder__encode_struct_field_value_T_json2_Node(encoder, (*val.current_node))')
|
||||
}
|
||||
|
||||
fn test_generate_c_extracts_array_sum_variant_for_generic_array_param() {
|
||||
csrc := generate_array_append_c_for_test_files([
|
||||
'
|
||||
module orm
|
||||
|
||||
pub struct InfixType {
|
||||
right int
|
||||
}
|
||||
|
||||
pub type Primitive = InfixType | []InfixType | int
|
||||
|
||||
pub fn tenant_filter_array_primitive_type[T](value []T) int {
|
||||
return value.len
|
||||
}
|
||||
|
||||
pub fn tenant_filter_primitive_type(value Primitive) int {
|
||||
return match value {
|
||||
InfixType {
|
||||
2
|
||||
}
|
||||
[]InfixType {
|
||||
tenant_filter_array_primitive_type(value)
|
||||
}
|
||||
int {
|
||||
3
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
'
|
||||
module main
|
||||
|
||||
import orm
|
||||
|
||||
fn main() {
|
||||
_ = orm.tenant_filter_primitive_type([]orm.InfixType{})
|
||||
}
|
||||
',
|
||||
])
|
||||
assert csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(((((Array_orm__InfixType*)(value._data._Array_orm__InfixType))')
|
||||
assert !csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(((((orm__InfixType*)(value._data._InfixType))')
|
||||
}
|
||||
|
||||
fn test_generate_c_lowers_global_interface_generic_method_call() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
interface Rng {
|
||||
intn(max int) !int
|
||||
}
|
||||
|
||||
__global default_rng &Rng
|
||||
|
||||
fn (mut rng Rng) element[T](array []T) !T {
|
||||
if array.len == 0 {
|
||||
return error("empty")
|
||||
}
|
||||
return array[rng.intn(array.len)!]
|
||||
}
|
||||
|
||||
fn element[T](array []T) !T {
|
||||
return default_rng.element[T](array)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = element[u8]([u8(1)]) or { u8(0) }
|
||||
}
|
||||
')
|
||||
assert csrc.contains('Rng__element_T_u8(default_rng, _v_array)')
|
||||
assert !csrc.contains('u8 _val = (_v_array)')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import strings
|
||||
import v2.ast
|
||||
|
||||
fn (mut g Gen) gen_for_stmt(node ast.ForStmt) {
|
||||
if node.init is ast.ForInStmt {
|
||||
if g.gen_map_for_in_stmt(node, node.init) {
|
||||
return
|
||||
}
|
||||
if g.gen_array_for_in_stmt(node, node.init) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if g.gen_transformed_untyped_map_for_in_stmt(node) {
|
||||
return
|
||||
}
|
||||
g.write_indent()
|
||||
has_init := !is_empty_stmt(node.init)
|
||||
has_cond := !is_empty_expr(node.cond)
|
||||
has_post := !is_empty_stmt(node.post)
|
||||
|
||||
if has_init || has_post {
|
||||
// C-style for loop: for (init; cond; post)
|
||||
g.sb.write_string('for (')
|
||||
if has_init {
|
||||
g.gen_stmt_inline(node.init)
|
||||
}
|
||||
g.sb.write_string('; ')
|
||||
if has_cond {
|
||||
g.expr(node.cond)
|
||||
}
|
||||
g.sb.write_string('; ')
|
||||
if has_post {
|
||||
g.gen_stmt_inline(node.post)
|
||||
}
|
||||
g.sb.writeln(') {')
|
||||
} else if has_cond {
|
||||
// while-style: for cond {
|
||||
g.sb.write_string('while (')
|
||||
g.expr(node.cond)
|
||||
g.sb.writeln(') {')
|
||||
} else {
|
||||
// Infinite loop: for {
|
||||
g.sb.writeln('for (;;) {')
|
||||
}
|
||||
|
||||
g.indent++
|
||||
// Save runtime_local_types: variables declared in the loop body
|
||||
// (e.g. _filter_it in nested map/filter) can shadow outer variables.
|
||||
// Without save/restore, the inner type overwrites the outer in the flat map.
|
||||
saved_local_types := g.runtime_local_types.clone()
|
||||
saved_decl_types := g.runtime_decl_types.clone()
|
||||
g.gen_stmts(node.stmts)
|
||||
g.runtime_local_types = saved_local_types.clone()
|
||||
g.runtime_decl_types = saved_decl_types.clone()
|
||||
g.not_local_var_cache.clear()
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_map_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool {
|
||||
mut map_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*')
|
||||
if (map_type == '' || map_type == 'int') && for_in.expr is ast.Ident {
|
||||
map_type = (g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*')
|
||||
}
|
||||
if !map_type.starts_with('Map_') {
|
||||
return false
|
||||
}
|
||||
key_type, value_type := g.parse_map_kv_types(map_type['Map_'.len..])
|
||||
if key_type == '' || value_type == '' {
|
||||
return false
|
||||
}
|
||||
id := g.tmp_counter
|
||||
g.tmp_counter++
|
||||
map_tmp := '_map_iter_${id}'
|
||||
len_tmp := '_map_len_${id}'
|
||||
idx_tmp := '_map_idx_${id}'
|
||||
delta_tmp := '_map_delta_${id}'
|
||||
mut expr_sb := strings.new_builder(64)
|
||||
saved_sb := g.sb
|
||||
g.sb = expr_sb
|
||||
g.expr(for_in.expr)
|
||||
map_expr := g.sb.str()
|
||||
g.sb = saved_sb
|
||||
g.write_indent()
|
||||
g.sb.writeln('${map_type} ${map_tmp} = ${map_expr};')
|
||||
g.write_indent()
|
||||
g.sb.writeln('int ${len_tmp} = ${map_tmp}.key_values.len;')
|
||||
g.write_indent()
|
||||
g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${len_tmp}); ${idx_tmp} = (${idx_tmp} + 1)) {')
|
||||
g.indent++
|
||||
g.write_indent()
|
||||
g.sb.writeln('int ${delta_tmp} = (${map_tmp}.key_values.len - ${len_tmp});')
|
||||
g.write_indent()
|
||||
g.sb.writeln('${len_tmp} = ${map_tmp}.key_values.len;')
|
||||
g.write_indent()
|
||||
g.sb.writeln('if ((${delta_tmp} < 0)) {')
|
||||
g.indent++
|
||||
g.write_indent()
|
||||
g.sb.writeln('${idx_tmp} = -1;')
|
||||
g.write_indent()
|
||||
g.sb.writeln('continue;')
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
g.write_indent()
|
||||
g.sb.writeln('if (!DenseArray__has_index(&${map_tmp}.key_values, ${idx_tmp})) {')
|
||||
g.indent++
|
||||
g.write_indent()
|
||||
g.sb.writeln('continue;')
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
key_name := if for_in.key is ast.Ident { for_in.key.name } else { '' }
|
||||
value_name := if for_in.value is ast.Ident { for_in.value.name } else { '' }
|
||||
saved_local_types := g.runtime_local_types.clone()
|
||||
saved_decl_types := g.runtime_decl_types.clone()
|
||||
if key_name != '' && key_name != '_' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${key_type} ${key_name} = *((${key_type}*)DenseArray__key(&${map_tmp}.key_values, ${idx_tmp}));')
|
||||
if key_type == 'string' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${key_name} = string__clone(${key_name});')
|
||||
}
|
||||
g.remember_runtime_local_type(key_name, key_type)
|
||||
}
|
||||
if value_name != '' && value_name != '_' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${value_type} ${value_name} = *((${value_type}*)DenseArray__value(&${map_tmp}.key_values, ${idx_tmp}));')
|
||||
if value_type == 'string' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${value_name} = string__clone(${value_name});')
|
||||
}
|
||||
g.remember_runtime_local_type(value_name, value_type)
|
||||
}
|
||||
g.gen_stmts(node.stmts)
|
||||
g.runtime_local_types = saved_local_types.clone()
|
||||
g.runtime_decl_types = saved_decl_types.clone()
|
||||
g.not_local_var_cache.clear()
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
return true
|
||||
}
|
||||
|
||||
fn cleanc_for_in_ident_name(expr ast.Expr) (string, bool) {
|
||||
if expr is ast.Ident {
|
||||
return expr.name, false
|
||||
}
|
||||
if expr is ast.ModifierExpr {
|
||||
if expr.expr is ast.Ident {
|
||||
return expr.expr.name, expr.kind == .key_mut
|
||||
}
|
||||
}
|
||||
return '', false
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_array_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool {
|
||||
mut array_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*')
|
||||
if (array_type == '' || array_type == 'int') && for_in.expr is ast.Ident {
|
||||
array_type =
|
||||
(g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*')
|
||||
}
|
||||
if array_type == '' {
|
||||
return false
|
||||
}
|
||||
mut array_decl_type := array_type
|
||||
if !c_type_is_array_value(array_decl_type) {
|
||||
alias_base := g.array_alias_base_type(array_decl_type)
|
||||
if alias_base != '' {
|
||||
array_decl_type = alias_base
|
||||
}
|
||||
}
|
||||
if !c_type_is_array_value(array_decl_type) {
|
||||
return false
|
||||
}
|
||||
elem_type := g.array_alias_elem_type_from_c_type(array_decl_type)
|
||||
if elem_type == '' {
|
||||
return false
|
||||
}
|
||||
id := g.tmp_counter
|
||||
g.tmp_counter++
|
||||
array_tmp := '_arr_iter_${id}'
|
||||
idx_tmp := '_arr_idx_${id}'
|
||||
mut expr_sb := strings.new_builder(64)
|
||||
saved_sb := g.sb
|
||||
g.sb = expr_sb
|
||||
g.expr(for_in.expr)
|
||||
array_expr := g.sb.str()
|
||||
g.sb = saved_sb
|
||||
g.write_indent()
|
||||
g.sb.writeln('${array_decl_type} ${array_tmp} = ${array_expr};')
|
||||
g.write_indent()
|
||||
g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${array_tmp}.len); ${idx_tmp} = (${idx_tmp} + 1)) {')
|
||||
g.indent++
|
||||
key_name, _ := cleanc_for_in_ident_name(for_in.key)
|
||||
value_name, value_is_mut := cleanc_for_in_ident_name(for_in.value)
|
||||
saved_local_types := g.runtime_local_types.clone()
|
||||
saved_decl_types := g.runtime_decl_types.clone()
|
||||
if key_name != '' && key_name != '_' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('int ${key_name} = ${idx_tmp};')
|
||||
g.remember_runtime_local_type(key_name, 'int')
|
||||
}
|
||||
if value_name != '' && value_name != '_' {
|
||||
g.write_indent()
|
||||
if value_is_mut {
|
||||
g.sb.writeln('${elem_type}* ${value_name} = &(((${elem_type}*)${array_tmp}.data)[${idx_tmp}]);')
|
||||
g.remember_runtime_local_type(value_name, '${elem_type}*')
|
||||
} else {
|
||||
g.sb.writeln('${elem_type} ${value_name} = ((${elem_type}*)${array_tmp}.data)[${idx_tmp}];')
|
||||
if elem_type == 'string' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${value_name} = string__clone(${value_name});')
|
||||
}
|
||||
g.remember_runtime_local_type(value_name, elem_type)
|
||||
}
|
||||
}
|
||||
g.gen_stmts(node.stmts)
|
||||
g.runtime_local_types = saved_local_types.clone()
|
||||
g.runtime_decl_types = saved_decl_types.clone()
|
||||
g.not_local_var_cache.clear()
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_transformed_untyped_map_for_in_stmt(node ast.ForStmt) bool {
|
||||
if node.init !is ast.AssignStmt || node.stmts.len == 0 {
|
||||
return false
|
||||
}
|
||||
init := node.init as ast.AssignStmt
|
||||
if init.lhs.len == 0 || init.lhs[0] !is ast.Ident {
|
||||
return false
|
||||
}
|
||||
key_name := (init.lhs[0] as ast.Ident).name
|
||||
first_stmt := node.stmts[0]
|
||||
if first_stmt !is ast.AssignStmt {
|
||||
return false
|
||||
}
|
||||
value_assign := first_stmt as ast.AssignStmt
|
||||
if value_assign.lhs.len == 0 || value_assign.rhs.len == 0 || value_assign.lhs[0] !is ast.Ident
|
||||
|| value_assign.rhs[0] !is ast.IndexExpr {
|
||||
return false
|
||||
}
|
||||
value_name := (value_assign.lhs[0] as ast.Ident).name
|
||||
index_expr := value_assign.rhs[0] as ast.IndexExpr
|
||||
mut map_type := g.get_expr_type(index_expr.lhs).trim_space().trim_right('*')
|
||||
if (map_type == '' || map_type == 'int') && index_expr.lhs is ast.Ident {
|
||||
map_type =
|
||||
(g.get_local_var_c_type(index_expr.lhs.name) or { '' }).trim_space().trim_right('*')
|
||||
}
|
||||
if !map_type.starts_with('Map_') {
|
||||
return false
|
||||
}
|
||||
map_node := ast.ForStmt{
|
||||
stmts: node.stmts[1..]
|
||||
}
|
||||
return g.gen_map_for_in_stmt(map_node, ast.ForInStmt{
|
||||
key: ast.Expr(ast.Ident{
|
||||
name: key_name
|
||||
})
|
||||
value: ast.Expr(ast.Ident{
|
||||
name: value_name
|
||||
})
|
||||
expr: index_expr.lhs
|
||||
})
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_stmt_inline(node ast.Stmt) {
|
||||
if !stmt_has_valid_data(node) {
|
||||
return
|
||||
}
|
||||
match node {
|
||||
ast.AssignStmt {
|
||||
lhs := node.lhs[0]
|
||||
rhs := node.rhs[0]
|
||||
if node.op == .decl_assign {
|
||||
mut name := ''
|
||||
if lhs is ast.Ident {
|
||||
name = lhs.name
|
||||
}
|
||||
typ := g.get_expr_type(rhs)
|
||||
g.sb.write_string('${typ} ${name} = ')
|
||||
g.expr(rhs)
|
||||
// Register the for-loop init variable so assignments inside
|
||||
// the loop body don't re-declare it.
|
||||
if name != '' && typ != '' {
|
||||
g.remember_runtime_local_type(name, typ)
|
||||
}
|
||||
} else {
|
||||
g.expr(lhs)
|
||||
op_str := match node.op {
|
||||
.assign { '=' }
|
||||
.plus_assign { '+=' }
|
||||
.minus_assign { '-=' }
|
||||
.mul_assign { '*=' }
|
||||
.div_assign { '/=' }
|
||||
.mod_assign { '%=' }
|
||||
.and_assign { '&=' }
|
||||
.or_assign { '|=' }
|
||||
.xor_assign { '^=' }
|
||||
.left_shift_assign { '<<=' }
|
||||
.right_shift_assign { '>>=' }
|
||||
else { '=' }
|
||||
}
|
||||
|
||||
g.sb.write_string(' ${op_str} ')
|
||||
g.expr(rhs)
|
||||
}
|
||||
}
|
||||
ast.ExprStmt {
|
||||
g.expr(node.expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module cleanc
|
||||
|
||||
fn test_generic_struct_scan_job_count_caps_runtime_jobs() {
|
||||
assert generic_struct_scan_job_count(128, 1000) == max_generic_struct_scan_jobs
|
||||
assert generic_struct_scan_job_count(64, 15) == 3
|
||||
assert generic_struct_scan_job_count(2, 1000) == 2
|
||||
assert generic_struct_scan_job_count(4, 0) == 0
|
||||
assert generic_struct_scan_job_count(0, 4) == 0
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import v2.ast
|
||||
|
||||
fn const_bool_value(expr ast.Expr) ?bool {
|
||||
match expr {
|
||||
ast.BasicLiteral {
|
||||
if expr.kind == .key_true {
|
||||
return true
|
||||
}
|
||||
if expr.kind == .key_false {
|
||||
return false
|
||||
}
|
||||
if expr.value == 'true' {
|
||||
return true
|
||||
}
|
||||
if expr.value == 'false' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if expr.name == 'true' {
|
||||
return true
|
||||
}
|
||||
if expr.name == 'false' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ast.Keyword {
|
||||
if expr.tok == .key_true {
|
||||
return true
|
||||
}
|
||||
if expr.tok == .key_false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.ModifierExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.CastExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.ComptimeExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.UnsafeExpr {
|
||||
if expr.stmts.len == 1 && expr.stmts[0] is ast.ExprStmt {
|
||||
stmt := expr.stmts[0] as ast.ExprStmt
|
||||
return const_bool_value(stmt.expr)
|
||||
}
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .not {
|
||||
if v := const_bool_value(expr.expr) {
|
||||
return !v
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_assign_value(name string, value_type string, expr ast.Expr) {
|
||||
g.write_indent()
|
||||
if value_type.starts_with('Array_fixed_') && !value_type.ends_with('*') {
|
||||
if expr is ast.CallExpr {
|
||||
if call_ret := g.get_call_return_type(expr.lhs, expr.args) {
|
||||
if call_ret == value_type {
|
||||
wrapper_type := g.c_fn_return_type_from_v(value_type)
|
||||
g.sb.write_string('{ ${wrapper_type} _tmp = ')
|
||||
g.expr(expr)
|
||||
g.sb.writeln('; memcpy(${name}, _tmp.ret_arr, sizeof(${value_type})); }')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
g.sb.write_string('memcpy(${name}, ')
|
||||
if expr is ast.ArrayInitExpr {
|
||||
g.sb.write_string('((${value_type})')
|
||||
g.expr(expr)
|
||||
g.sb.write_string(')')
|
||||
} else {
|
||||
g.expr(expr)
|
||||
}
|
||||
g.sb.writeln(', sizeof(${value_type}));')
|
||||
return
|
||||
}
|
||||
g.sb.write_string('${name} = ')
|
||||
if g.get_sum_type_variants_for(value_type).len > 0 {
|
||||
g.gen_type_cast_expr(value_type, expr)
|
||||
} else {
|
||||
g.expr(expr)
|
||||
}
|
||||
g.sb.writeln(';')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_decl_if_expr_branch(name string, value_type string, stmts []ast.Stmt) {
|
||||
last_idx := last_non_empty_stmt_index(stmts)
|
||||
if last_idx < 0 {
|
||||
return
|
||||
}
|
||||
for i, stmt in stmts {
|
||||
if stmt is ast.EmptyStmt {
|
||||
continue
|
||||
}
|
||||
if i == last_idx && stmt is ast.ExprStmt {
|
||||
if stmt.expr is ast.IfExpr {
|
||||
nested_if := stmt.expr as ast.IfExpr
|
||||
if !g.if_expr_can_be_ternary(&nested_if) && nested_if.else_expr !is ast.EmptyExpr {
|
||||
g.gen_decl_if_expr(name, value_type, &nested_if)
|
||||
continue
|
||||
}
|
||||
if nested_if.else_expr is ast.EmptyExpr {
|
||||
if payload_expr := g.unsafe_expr_or_payload_value(stmts) {
|
||||
g.gen_stmt(stmt)
|
||||
g.gen_if_expr_assign_value(name, value_type, payload_expr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
// When the last expression is a void call (e.g. array__push from <<),
|
||||
// emit it as a standalone statement without assigning to the temp var.
|
||||
if g.expr_is_void_call(stmt.expr) {
|
||||
g.write_indent()
|
||||
g.expr(stmt.expr)
|
||||
g.sb.writeln(';')
|
||||
} else {
|
||||
g.gen_if_expr_assign_value(name, value_type, stmt.expr)
|
||||
}
|
||||
} else {
|
||||
g.gen_stmt(stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_scoped_decl_if_expr_branch(name string, value_type string, stmts []ast.Stmt) {
|
||||
saved_runtime_local_types := g.runtime_local_types.clone()
|
||||
saved_runtime_decl_types := g.runtime_decl_types.clone()
|
||||
saved_not_local_var_cache := g.not_local_var_cache.clone()
|
||||
g.gen_decl_if_expr_branch(name, value_type, stmts)
|
||||
g.runtime_local_types = saved_runtime_local_types.clone()
|
||||
g.runtime_decl_types = saved_runtime_decl_types.clone()
|
||||
g.not_local_var_cache = saved_not_local_var_cache.clone()
|
||||
}
|
||||
|
||||
// expr_is_void_call checks if the expression is a function call that returns void.
|
||||
// Used to prevent assigning void results to temp variables in if-expression branches.
|
||||
fn (mut g Gen) expr_is_void_call(expr ast.Expr) bool {
|
||||
if expr is ast.CallExpr {
|
||||
if ret := g.get_call_return_type(expr.lhs, expr.args) {
|
||||
return ret == 'void'
|
||||
}
|
||||
// Check resolved name
|
||||
c_name := g.resolve_call_name(expr.lhs, expr.args.len)
|
||||
if c_name != '' {
|
||||
if ret := g.fn_return_types[c_name] {
|
||||
return ret == 'void'
|
||||
}
|
||||
}
|
||||
// panic/v_panic always returns void (noreturn)
|
||||
if expr.lhs is ast.Ident {
|
||||
if expr.lhs.name in ['panic', 'v_panic'] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_decl_if_expr(name string, value_type string, if_expr &ast.IfExpr) {
|
||||
if if_expr.cond is ast.EmptyExpr {
|
||||
g.gen_scoped_decl_if_expr_branch(name, value_type, if_expr.stmts)
|
||||
return
|
||||
}
|
||||
g.write_indent()
|
||||
g.sb.write_string('if (')
|
||||
g.expr(if_expr.cond)
|
||||
g.sb.writeln(') {')
|
||||
g.indent++
|
||||
g.gen_scoped_decl_if_expr_branch(name, value_type, if_expr.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
if if_expr.else_expr is ast.IfExpr {
|
||||
else_if := if_expr.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_scoped_decl_if_expr_branch(name, value_type, else_if.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_decl_if_expr(name, value_type, &else_if)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
}
|
||||
} else if if_expr.else_expr !is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_if_expr_assign_value(name, value_type, if_expr.else_expr)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln('')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_return_if_branch(stmts []ast.Stmt) {
|
||||
if stmts.len == 0 {
|
||||
return
|
||||
}
|
||||
for i, stmt in stmts {
|
||||
if i == stmts.len - 1 && stmt is ast.ExprStmt {
|
||||
if stmt.expr is ast.IfExpr {
|
||||
nested_if := stmt.expr as ast.IfExpr
|
||||
if !g.if_expr_can_be_ternary(&nested_if) && nested_if.else_expr !is ast.EmptyExpr {
|
||||
g.gen_return_if_expr(nested_if, true)
|
||||
continue
|
||||
}
|
||||
}
|
||||
mut ret_exprs := []ast.Expr{cap: 1}
|
||||
ret_exprs << stmt.expr
|
||||
ret_stmt := ast.ReturnStmt{
|
||||
exprs: ret_exprs
|
||||
}
|
||||
prev_suppress := g.suppress_return_drop_emit
|
||||
g.suppress_return_drop_emit = true
|
||||
g.gen_stmt(ret_stmt)
|
||||
g.suppress_return_drop_emit = prev_suppress
|
||||
} else {
|
||||
g.gen_stmt(stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_return_if_expr(if_expr ast.IfExpr, emit_indent bool) {
|
||||
if if_expr.cond is ast.EmptyExpr {
|
||||
g.gen_return_if_branch(if_expr.stmts)
|
||||
return
|
||||
}
|
||||
if emit_indent {
|
||||
g.write_indent()
|
||||
}
|
||||
g.sb.write_string('if (')
|
||||
g.expr(if_expr.cond)
|
||||
g.sb.writeln(') {')
|
||||
g.indent++
|
||||
g.gen_return_if_branch(if_expr.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
if if_expr.else_expr is ast.IfExpr {
|
||||
else_if := if_expr.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_return_if_branch(else_if.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_return_if_expr(else_if, true)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
}
|
||||
} else if if_expr.else_expr !is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
mut else_exprs := []ast.Expr{cap: 1}
|
||||
else_exprs << if_expr.else_expr
|
||||
else_stmt := ast.ReturnStmt{
|
||||
exprs: else_exprs
|
||||
}
|
||||
prev_suppress := g.suppress_return_drop_emit
|
||||
g.suppress_return_drop_emit = true
|
||||
g.gen_stmt(else_stmt)
|
||||
g.suppress_return_drop_emit = prev_suppress
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln('')
|
||||
}
|
||||
}
|
||||
|
||||
fn (g &Gen) if_expr_can_be_ternary(node &ast.IfExpr) bool {
|
||||
if node.cond !is ast.EmptyExpr {
|
||||
if node.stmts.len != 1 || node.stmts[0] !is ast.ExprStmt {
|
||||
return false
|
||||
}
|
||||
if node.else_expr is ast.EmptyExpr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if node.cond is ast.EmptyExpr {
|
||||
return node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt
|
||||
}
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
return g.if_expr_can_be_ternary(&else_if)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn (g &Gen) extract_if_expr(expr ast.Expr) ?ast.IfExpr {
|
||||
match expr {
|
||||
ast.IfExpr {
|
||||
return expr
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return g.extract_if_expr(expr.expr)
|
||||
}
|
||||
ast.ModifierExpr {
|
||||
return g.extract_if_expr(expr.expr)
|
||||
}
|
||||
ast.UnsafeExpr {
|
||||
if expr.stmts.len == 1 {
|
||||
stmt0 := expr.stmts[0]
|
||||
if stmt0 is ast.ExprStmt {
|
||||
expr_stmt := stmt0 as ast.ExprStmt
|
||||
return g.extract_if_expr(expr_stmt.expr)
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
else {
|
||||
return none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last_non_empty_stmt_index(stmts []ast.Stmt) int {
|
||||
mut idx := stmts.len - 1
|
||||
for idx >= 0 && stmts[idx] is ast.EmptyStmt {
|
||||
idx--
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
fn (mut g Gen) branch_result_type(stmts []ast.Stmt) string {
|
||||
last_idx := last_non_empty_stmt_index(stmts)
|
||||
if last_idx < 0 {
|
||||
return ''
|
||||
}
|
||||
last := stmts[last_idx]
|
||||
match last {
|
||||
ast.BlockStmt {
|
||||
return g.branch_result_type(last.stmts)
|
||||
}
|
||||
ast.ExprStmt {
|
||||
if nested := g.extract_if_expr(last.expr) {
|
||||
return g.get_if_expr_type(&nested)
|
||||
}
|
||||
mut typ := g.get_expr_type(last.expr)
|
||||
if (typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr')
|
||||
&& last.expr is ast.Ident {
|
||||
if local_typ := g.branch_declared_ident_type(stmts, last_idx, last.expr.name) {
|
||||
typ = local_typ
|
||||
}
|
||||
}
|
||||
if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' {
|
||||
if raw := g.get_raw_type(last.expr) {
|
||||
raw_type := g.types_type_to_c(raw)
|
||||
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
|
||||
typ = raw_type
|
||||
}
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) branch_declared_ident_type(stmts []ast.Stmt, before_idx int, name string) ?string {
|
||||
if name == '' {
|
||||
return none
|
||||
}
|
||||
mut i := before_idx - 1
|
||||
for i >= 0 {
|
||||
stmt := stmts[i]
|
||||
if stmt is ast.AssignStmt {
|
||||
if stmt.op == .decl_assign && stmt.lhs.len == stmt.rhs.len {
|
||||
for j, lhs in stmt.lhs {
|
||||
if lhs is ast.Ident && lhs.name == name {
|
||||
mut typ := g.get_expr_type(stmt.rhs[j])
|
||||
if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' {
|
||||
if raw := g.get_raw_type(stmt.rhs[j]) {
|
||||
raw_type := g.types_type_to_c(raw)
|
||||
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
|
||||
typ = raw_type
|
||||
}
|
||||
}
|
||||
}
|
||||
if typ != '' && typ != 'int' && typ != 'void*' && typ != 'voidptr' {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i--
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_stmt(node &ast.IfExpr) {
|
||||
// Skip empty conditions (pure else blocks shouldn't appear at top level)
|
||||
if node.cond is ast.EmptyExpr {
|
||||
return
|
||||
}
|
||||
if cond_value := const_bool_value(node.cond) {
|
||||
if cond_value {
|
||||
g.gen_scoped_stmts(node.stmts)
|
||||
return
|
||||
}
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.gen_scoped_stmts(else_if.stmts)
|
||||
} else {
|
||||
g.gen_if_expr_stmt(&else_if)
|
||||
}
|
||||
} else if node.else_expr !is ast.EmptyExpr {
|
||||
g.gen_scoped_expr_stmts(node.else_expr)
|
||||
}
|
||||
return
|
||||
}
|
||||
g.sb.write_string('if (')
|
||||
g.expr(node.cond)
|
||||
g.sb.writeln(') {')
|
||||
g.indent++
|
||||
g.gen_scoped_stmts(node.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
// Handle else / else-if
|
||||
if node.else_expr !is ast.EmptyExpr {
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_scoped_stmts(else_if.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
} else {
|
||||
g.sb.write_string(' else ')
|
||||
g.gen_if_expr_stmt(&else_if)
|
||||
}
|
||||
} else {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_scoped_expr_stmts(node.else_expr)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
}
|
||||
}
|
||||
g.sb.writeln('')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_ternary(node &ast.IfExpr) {
|
||||
if node.cond is ast.EmptyExpr {
|
||||
if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt {
|
||||
stmt := node.stmts[0] as ast.ExprStmt
|
||||
g.expr(stmt.expr)
|
||||
} else {
|
||||
g.sb.write_string('0')
|
||||
}
|
||||
return
|
||||
}
|
||||
g.sb.write_string('(')
|
||||
g.expr(node.cond)
|
||||
g.sb.write_string(' ? ')
|
||||
if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt {
|
||||
stmt := node.stmts[0] as ast.ExprStmt
|
||||
if nested := g.extract_if_expr(stmt.expr) {
|
||||
g.gen_if_expr_value(&nested)
|
||||
} else {
|
||||
g.expr(stmt.expr)
|
||||
}
|
||||
} else {
|
||||
g.sb.write_string('0')
|
||||
}
|
||||
g.sb.write_string(' : ')
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
g.gen_if_expr_value(&else_if)
|
||||
} else if node.else_expr is ast.EmptyExpr {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
if nested := g.extract_if_expr(node.else_expr) {
|
||||
g.gen_if_expr_value(&nested)
|
||||
} else {
|
||||
g.expr(node.else_expr)
|
||||
}
|
||||
}
|
||||
g.sb.write_string(')')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_value(node &ast.IfExpr) {
|
||||
mut value_type := g.get_if_expr_type(node)
|
||||
if value_type == '' || value_type == 'void' {
|
||||
g.sb.write_string('({ ')
|
||||
g.gen_if_expr_stmt(node)
|
||||
g.sb.write_string('; 0; })')
|
||||
return
|
||||
}
|
||||
if value_type == 'int_literal' {
|
||||
value_type = 'int'
|
||||
}
|
||||
tmp_name := '_if_expr_t${g.tmp_counter}'
|
||||
g.tmp_counter++
|
||||
g.sb.write_string('({ ${value_type} ${tmp_name} = ${zero_value_for_type(value_type)}; ')
|
||||
g.gen_decl_if_expr(tmp_name, value_type, node)
|
||||
if value_type.starts_with('Array_fixed_') && !value_type.ends_with('*') {
|
||||
_, fixed_len := parse_fixed_array_elem_type(value_type)
|
||||
if fixed_len > 0 {
|
||||
g.sb.write_string(' (${value_type}){')
|
||||
for i in 0 .. fixed_len {
|
||||
if i > 0 {
|
||||
g.sb.write_string(', ')
|
||||
}
|
||||
g.sb.write_string('${tmp_name}[${i}]')
|
||||
}
|
||||
g.sb.write_string('}; })')
|
||||
return
|
||||
}
|
||||
}
|
||||
g.sb.write_string(' ${tmp_name}; })')
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_if_expr_type(node &ast.IfExpr) string {
|
||||
mut env_type := ''
|
||||
if t := g.get_expr_type_from_env(ast.Expr(*node)) {
|
||||
if t != '' {
|
||||
env_type = t
|
||||
}
|
||||
}
|
||||
mut branch_type := g.branch_result_type(node.stmts)
|
||||
if branch_type == 'int_literal' {
|
||||
branch_type = 'int'
|
||||
}
|
||||
mut else_type := ''
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
else_type = g.get_if_expr_type(&else_if)
|
||||
} else if node.else_expr !is ast.EmptyExpr {
|
||||
if nested := g.extract_if_expr(node.else_expr) {
|
||||
else_type = g.get_if_expr_type(&nested)
|
||||
} else {
|
||||
else_type = g.get_expr_type(node.else_expr)
|
||||
if else_type == '' || else_type == 'int' || else_type == 'void*'
|
||||
|| else_type == 'voidptr' {
|
||||
if raw := g.get_raw_type(node.else_expr) {
|
||||
raw_type := g.types_type_to_c(raw)
|
||||
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
|
||||
else_type = raw_type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if else_type == 'int_literal' {
|
||||
else_type = 'int'
|
||||
}
|
||||
if branch_type != '' && else_type != '' && branch_type == else_type {
|
||||
if branch_type != 'int' || env_type == '' || env_type == 'int' || env_type == 'void*'
|
||||
|| env_type == 'voidptr' {
|
||||
return branch_type
|
||||
}
|
||||
}
|
||||
if branch_type != '' && branch_type != 'int' {
|
||||
if env_type == '' || env_type == 'int' || branch_type.starts_with('${env_type}_T_') {
|
||||
return branch_type
|
||||
}
|
||||
}
|
||||
if env_type != '' {
|
||||
if g.cur_fn_ret_type.starts_with('${env_type}_T_') {
|
||||
return g.cur_fn_ret_type
|
||||
}
|
||||
return env_type
|
||||
}
|
||||
if else_type != '' {
|
||||
return else_type
|
||||
}
|
||||
return 'int'
|
||||
}
|
||||
|
||||
// expr_produces_pointer checks if an expression produces a pointer value in C.
|
||||
// More comprehensive than expr_is_pointer - handles casts, pointer arithmetic, etc.
|
||||
// Used in gen_call_arg to avoid wrapping pointer expressions in compound literals.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
|
||||
fn (g &Gen) print_cgen_mem(stage string) {
|
||||
if os.getenv('V2_MEM') == '' {
|
||||
return
|
||||
}
|
||||
$if macos {
|
||||
eprintln(' [mem] cleanc/${stage}: live ${darwin_cleanc_live_mb()} MB')
|
||||
} $else {
|
||||
eprintln(' [mem] cleanc/${stage}')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module cleanc
|
||||
|
||||
#include <malloc/malloc.h>
|
||||
|
||||
struct C.malloc_statistics_t {
|
||||
blocks_in_use u32
|
||||
size_in_use usize
|
||||
max_size_in_use usize
|
||||
size_allocated usize
|
||||
}
|
||||
|
||||
fn C.malloc_default_zone() voidptr
|
||||
fn C.malloc_zone_statistics(zone voidptr, stats &C.malloc_statistics_t)
|
||||
|
||||
fn darwin_cleanc_live_mb() u64 {
|
||||
mut st := C.malloc_statistics_t{}
|
||||
C.malloc_zone_statistics(C.malloc_default_zone(), &st)
|
||||
return u64(st.size_in_use) / (1024 * 1024)
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
import v2.parser
|
||||
import v2.pref as vpref
|
||||
import v2.token
|
||||
import v2.transformer
|
||||
import v2.types
|
||||
|
||||
fn module_storage_csrc_for_test_sources(sources map[string]string) string {
|
||||
tmp_dir := os.join_path(os.temp_dir(), 'v2_module_storage_codegen_${os.getpid()}')
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
mut paths := []string{}
|
||||
for rel_path, code in sources {
|
||||
tmp_file := os.join_path(tmp_dir, rel_path)
|
||||
os.mkdir_all(os.dir(tmp_file)) or { panic('failed to create temp source dir') }
|
||||
os.write_file(tmp_file, code) or { panic('failed to write temp source') }
|
||||
paths << tmp_file
|
||||
}
|
||||
prefs := &vpref.Preferences{
|
||||
backend: .cleanc
|
||||
no_parallel: true
|
||||
}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut par := parser.Parser.new(prefs)
|
||||
files := par.parse_files(paths, mut file_set)
|
||||
env := types.Environment.new()
|
||||
mut checker := types.Checker.new(prefs, file_set, env)
|
||||
checker.check_files(files)
|
||||
mut trans := transformer.Transformer.new_with_pref(env, prefs)
|
||||
trans.set_file_set(file_set)
|
||||
transformed_files := trans.transform_files(files)
|
||||
mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs)
|
||||
return gen.gen()
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_module_prefix_for_qualified_module_storage() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'report/report.v': 'module report
|
||||
|
||||
pub __global mut errors = 0
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import report
|
||||
|
||||
fn main() {
|
||||
report.errors += 1
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('int report__errors'), csrc
|
||||
assert csrc.contains('report__errors'), csrc
|
||||
assert !csrc.contains('\nint errors'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_resolves_module_storage_import_alias() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'report/report.v': 'module report
|
||||
|
||||
pub __global mut errors = 0
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import report as r
|
||||
|
||||
fn main() {
|
||||
r.errors += 1
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('report__errors'), csrc
|
||||
assert !csrc.contains('r__errors'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_same_short_module_storage_names_distinct() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'a/a.v': 'module a
|
||||
|
||||
pub __global state = 1
|
||||
'
|
||||
'b/b.v': 'module b
|
||||
|
||||
pub __global state = 2
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import a
|
||||
import b
|
||||
|
||||
fn main() {
|
||||
_ = a.state + b.state
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('int a__state = 1;'), csrc
|
||||
assert csrc.contains('int b__state = 2;'), csrc
|
||||
assert !csrc.contains('\nint state ='), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_prefixes_module_storage_names_containing_dunders() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'a/a.v': 'module a
|
||||
|
||||
pub __global mut state__x = 1
|
||||
|
||||
pub fn bump() int {
|
||||
state__x += 1
|
||||
return state__x
|
||||
}
|
||||
'
|
||||
'b/b.v': 'module b
|
||||
|
||||
pub __global mut state__x = 2
|
||||
|
||||
pub fn bump() int {
|
||||
state__x += 1
|
||||
return state__x
|
||||
}
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import a
|
||||
import b
|
||||
|
||||
fn main() {
|
||||
_ = a.bump() + b.bump()
|
||||
_ = a.state__x + b.state__x
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('int a__state__x = 1;'), csrc
|
||||
assert csrc.contains('int b__state__x = 2;'), csrc
|
||||
assert csrc.contains('a__state__x += 1;'), csrc
|
||||
assert csrc.contains('return a__state__x;'), csrc
|
||||
assert csrc.contains('b__state__x += 1;'), csrc
|
||||
assert csrc.contains('return b__state__x;'), csrc
|
||||
assert !csrc.contains('\nint state__x ='), csrc
|
||||
assert !csrc.contains('\n\tstate__x += 1;'), csrc
|
||||
assert !csrc.contains('\n\treturn state__x;'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_c_global_declaration_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'main.v': 'module main
|
||||
|
||||
@[c_extern]
|
||||
__global C.errno int
|
||||
|
||||
@[c_extern]
|
||||
__global C.stdin voidptr
|
||||
|
||||
@[c_extern]
|
||||
__global C.stdout voidptr
|
||||
|
||||
@[c_extern]
|
||||
__global C.stderr voidptr
|
||||
|
||||
fn main() {
|
||||
_ = C.errno
|
||||
_ = C.stdin
|
||||
_ = C.stdout
|
||||
_ = C.stderr
|
||||
}
|
||||
'
|
||||
})
|
||||
assert !csrc.contains('main__C.errno'), csrc
|
||||
assert !csrc.contains('main__errno'), csrc
|
||||
assert !csrc.contains('main__stdin'), csrc
|
||||
assert !csrc.contains('main__stdout'), csrc
|
||||
assert !csrc.contains('main__stderr'), csrc
|
||||
assert csrc.contains('errno'), csrc
|
||||
assert csrc.contains('stdin'), csrc
|
||||
assert csrc.contains('stdout'), csrc
|
||||
assert csrc.contains('stderr'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_c_extern_global_attributes_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'main.v': 'module main
|
||||
|
||||
@[c_extern; export: "raw_counter"; weak; hidden; markused]
|
||||
__global raw_counter int
|
||||
|
||||
fn main() {
|
||||
_ = raw_counter
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_counter;'), csrc
|
||||
assert csrc.contains('raw_counter'), csrc
|
||||
assert !csrc.contains('main__raw_counter'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_translated_c_v_extern_global_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'cwrap/runtime.c.v': '@[translated]
|
||||
module cwrap
|
||||
|
||||
@[c_extern; hidden; markused]
|
||||
__global raw_status int
|
||||
|
||||
pub fn read_status() int {
|
||||
return raw_status
|
||||
}
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import cwrap
|
||||
|
||||
fn main() {
|
||||
_ = cwrap.read_status()
|
||||
_ = cwrap.raw_status
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_status;'), csrc
|
||||
assert csrc.contains('return raw_status;'), csrc
|
||||
assert csrc.count('raw_status') >= 3, csrc
|
||||
assert !csrc.contains('cwrap__raw_status'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_qualified_c_extern_global_alias_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'cwrap/runtime.c.v': '@[translated]
|
||||
module cwrap
|
||||
|
||||
@[c_extern; hidden; markused]
|
||||
__global raw_status int
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import cwrap as cw
|
||||
|
||||
fn main() {
|
||||
_ = cw.raw_status
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_status;'), csrc
|
||||
assert csrc.count('raw_status') >= 2, csrc
|
||||
assert !csrc.contains('cwrap__raw_status'), csrc
|
||||
assert !csrc.contains('cw__raw_status'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_plain_module_c_extern_global_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'ext/ext.v': 'module ext
|
||||
|
||||
@[c_extern]
|
||||
pub __global raw_status int
|
||||
|
||||
pub fn read_status() int {
|
||||
return raw_status
|
||||
}
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import ext
|
||||
|
||||
fn main() {
|
||||
_ = ext.raw_status
|
||||
_ = ext.read_status()
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_status;'), csrc
|
||||
assert csrc.contains('return raw_status;'), csrc
|
||||
assert csrc.contains('(void)(raw_status);'), csrc
|
||||
assert !csrc.contains('ext__raw_status'), csrc
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import v2.ast
|
||||
|
||||
fn (mut g Gen) try_gen_orm_create_call(c_name string, call_args []ast.Expr) bool {
|
||||
if call_args.len != 3 || !c_name.ends_with('__create') {
|
||||
return false
|
||||
}
|
||||
if !orm_table_can_emit(call_args[1]) || !orm_table_fields_array_can_emit(call_args[2]) {
|
||||
return false
|
||||
}
|
||||
g.sb.write_string('${c_name}(')
|
||||
g.gen_call_arg(c_name, 0, call_args[0])
|
||||
g.sb.write_string(', ')
|
||||
g.write_orm_table_c_expr(call_args[1])
|
||||
g.sb.write_string(', ')
|
||||
g.write_orm_table_fields_array_c_expr(call_args[2])
|
||||
g.sb.write_string(')')
|
||||
if c_name != '' {
|
||||
g.mark_called_fn_name(c_name)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut g Gen) try_gen_orm_create_call_expr(node ast.CallExpr) bool {
|
||||
if !orm_create_call_can_emit(node) {
|
||||
return false
|
||||
}
|
||||
lhs := node.lhs as ast.SelectorExpr
|
||||
name := g.orm_create_call_name(node) or { return false }
|
||||
call_args := [
|
||||
lhs.lhs,
|
||||
node.args[0],
|
||||
node.args[1],
|
||||
]
|
||||
return g.try_gen_orm_create_call(name, call_args)
|
||||
}
|
||||
|
||||
fn orm_create_call_can_emit(node ast.CallExpr) bool {
|
||||
if node.args.len != 2 || node.lhs !is ast.SelectorExpr {
|
||||
return false
|
||||
}
|
||||
lhs := node.lhs as ast.SelectorExpr
|
||||
return lhs.rhs.name == 'create' && orm_table_can_emit(node.args[0])
|
||||
&& orm_table_fields_array_can_emit(node.args[1])
|
||||
}
|
||||
|
||||
fn (mut g Gen) orm_create_call_name(node ast.CallExpr) ?string {
|
||||
if node.lhs !is ast.SelectorExpr {
|
||||
return none
|
||||
}
|
||||
lhs := node.lhs as ast.SelectorExpr
|
||||
method_name := sanitize_fn_ident(lhs.rhs.name)
|
||||
mut name := g.resolve_call_name(node.lhs, node.args.len)
|
||||
if name != '' {
|
||||
return name
|
||||
}
|
||||
mut base_type := g.method_receiver_base_type(lhs.lhs)
|
||||
if base_type == '' {
|
||||
base_type = g.get_expr_type(lhs.lhs).trim_space().trim_right('*')
|
||||
}
|
||||
if base_type == '' {
|
||||
base_type = g.receiver_local_base_type(lhs.lhs)
|
||||
}
|
||||
if name == '' && base_type != '' {
|
||||
if embedded := g.resolve_method_on_embedded_receiver(base_type, method_name) {
|
||||
name = embedded.method_c_name
|
||||
}
|
||||
}
|
||||
if name == '' && base_type != '' {
|
||||
name = '${base_type}__${method_name}'
|
||||
}
|
||||
if name == '' {
|
||||
return none
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn (mut g Gen) orm_create_call_result_type(node ast.CallExpr) ?string {
|
||||
if !orm_create_call_can_emit(node) {
|
||||
return none
|
||||
}
|
||||
name := g.orm_create_call_name(node) or { return none }
|
||||
if ret := g.fn_return_types[name] {
|
||||
return ret
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_table_can_emit(expr ast.Expr) bool {
|
||||
init := orm_init_expr(expr) or { return false }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return false }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return false }
|
||||
return orm_string_value(name_expr) != none && orm_vattribute_array_can_emit(attrs_expr)
|
||||
}
|
||||
|
||||
fn orm_table_fields_array_can_emit(expr ast.Expr) bool {
|
||||
arr := orm_array_init_expr(expr) or { return false }
|
||||
for field_expr in arr.exprs {
|
||||
if !orm_table_field_can_emit(field_expr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn orm_table_field_can_emit(expr ast.Expr) bool {
|
||||
init := orm_init_expr(expr) or { return false }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return false }
|
||||
typ_expr := orm_init_field_value(init, 'typ') or { return false }
|
||||
nullable_expr := orm_init_field_value(init, 'nullable') or { return false }
|
||||
default_val_expr := orm_init_field_value(init, 'default_val') or { return false }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return false }
|
||||
is_arr_expr := orm_init_field_value(init, 'is_arr') or { return false }
|
||||
return orm_string_value(name_expr) != none && orm_number_c_expr(typ_expr) != none
|
||||
&& orm_bool_c_expr(nullable_expr) != none && orm_string_value(default_val_expr) != none
|
||||
&& orm_vattribute_array_can_emit(attrs_expr) && orm_bool_c_expr(is_arr_expr) != none
|
||||
}
|
||||
|
||||
fn orm_vattribute_array_can_emit(expr ast.Expr) bool {
|
||||
arr := orm_array_init_expr(expr) or { return false }
|
||||
for attr_expr in arr.exprs {
|
||||
if !orm_vattribute_can_emit(attr_expr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn orm_vattribute_can_emit(expr ast.Expr) bool {
|
||||
init := orm_init_expr(expr) or { return false }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return false }
|
||||
has_arg_expr := orm_init_field_value(init, 'has_arg') or { return false }
|
||||
arg_expr := orm_init_field_value(init, 'arg') or { return false }
|
||||
kind_expr := orm_init_field_value(init, 'kind') or { return false }
|
||||
return orm_string_value(name_expr) != none && orm_bool_c_expr(has_arg_expr) != none
|
||||
&& orm_string_value(arg_expr) != none && orm_ident_c_expr(kind_expr) != none
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_table_c_expr(expr ast.Expr) {
|
||||
init := orm_init_expr(expr) or { return }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return }
|
||||
g.sb.write_string('((orm__Table){.name = ')
|
||||
g.write_orm_v_string_c_expr(name_expr)
|
||||
g.sb.write_string(',.attrs = ')
|
||||
g.write_orm_vattribute_array_c_expr(attrs_expr)
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_table_fields_array_c_expr(expr ast.Expr) {
|
||||
arr := orm_array_init_expr(expr) or { return }
|
||||
g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(orm__TableField), ')
|
||||
if arr.exprs.len == 0 {
|
||||
g.sb.write_string('&(array){0})')
|
||||
return
|
||||
}
|
||||
g.sb.write_string('&(orm__TableField[${arr.exprs.len}]){')
|
||||
for i, field_expr in arr.exprs {
|
||||
if i > 0 {
|
||||
g.sb.write_string(', ')
|
||||
}
|
||||
g.write_orm_table_field_c_expr(field_expr)
|
||||
}
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_table_field_c_expr(expr ast.Expr) {
|
||||
init := orm_init_expr(expr) or { return }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return }
|
||||
typ_expr := orm_init_field_value(init, 'typ') or { return }
|
||||
nullable_expr := orm_init_field_value(init, 'nullable') or { return }
|
||||
default_val_expr := orm_init_field_value(init, 'default_val') or { return }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return }
|
||||
is_arr_expr := orm_init_field_value(init, 'is_arr') or { return }
|
||||
g.sb.write_string('((orm__TableField){.name = ')
|
||||
g.write_orm_v_string_c_expr(name_expr)
|
||||
g.sb.write_string(',.typ = ')
|
||||
g.sb.write_string(orm_number_c_expr(typ_expr) or { return })
|
||||
g.sb.write_string(',.nullable = ')
|
||||
g.sb.write_string(orm_bool_c_expr(nullable_expr) or { return })
|
||||
g.sb.write_string(',.default_val = ')
|
||||
g.write_orm_v_string_c_expr(default_val_expr)
|
||||
g.sb.write_string(',.attrs = ')
|
||||
g.write_orm_vattribute_array_c_expr(attrs_expr)
|
||||
g.sb.write_string(',.is_arr = ')
|
||||
g.sb.write_string(orm_bool_c_expr(is_arr_expr) or { return })
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_vattribute_array_c_expr(expr ast.Expr) {
|
||||
arr := orm_array_init_expr(expr) or { return }
|
||||
if arr.exprs.len == 0 {
|
||||
g.sb.write_string('new_array_from_c_array(0, 0, sizeof(VAttribute), &(array){0})')
|
||||
return
|
||||
}
|
||||
g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(VAttribute), &(VAttribute[${arr.exprs.len}]){')
|
||||
for i, attr_expr in arr.exprs {
|
||||
if i > 0 {
|
||||
g.sb.write_string(', ')
|
||||
}
|
||||
g.write_orm_vattribute_c_expr(attr_expr)
|
||||
}
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_vattribute_c_expr(expr ast.Expr) {
|
||||
init := orm_init_expr(expr) or { return }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return }
|
||||
has_arg_expr := orm_init_field_value(init, 'has_arg') or { return }
|
||||
arg_expr := orm_init_field_value(init, 'arg') or { return }
|
||||
kind_expr := orm_init_field_value(init, 'kind') or { return }
|
||||
g.sb.write_string('((VAttribute){.name = ')
|
||||
g.write_orm_v_string_c_expr(name_expr)
|
||||
g.sb.write_string(',.has_arg = ')
|
||||
g.sb.write_string(orm_bool_c_expr(has_arg_expr) or { return })
|
||||
g.sb.write_string(',.arg = ')
|
||||
g.write_orm_v_string_c_expr(arg_expr)
|
||||
g.sb.write_string(',.kind = ')
|
||||
g.sb.write_string(orm_ident_c_expr(kind_expr) or { return })
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn orm_init_expr(expr ast.Expr) ?ast.InitExpr {
|
||||
match expr {
|
||||
ast.InitExpr {
|
||||
return expr
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return orm_init_expr(expr.expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_array_init_expr(expr ast.Expr) ?ast.ArrayInitExpr {
|
||||
match expr {
|
||||
ast.ArrayInitExpr {
|
||||
return expr
|
||||
}
|
||||
ast.CallExpr {
|
||||
name := orm_call_name(expr.lhs)
|
||||
if name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array', 'new_array_from_c_array']
|
||||
&& expr.args.len >= 4 {
|
||||
return orm_array_init_expr(expr.args[3])
|
||||
}
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .amp {
|
||||
return orm_array_init_expr(expr.expr)
|
||||
}
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return orm_array_init_expr(expr.expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_call_name(lhs ast.Expr) string {
|
||||
match lhs {
|
||||
ast.Ident {
|
||||
return lhs.name
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
if lhs.lhs is ast.Ident {
|
||||
left := lhs.lhs as ast.Ident
|
||||
return '${left.name}__${lhs.rhs.name}'
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
fn orm_init_field_value(init ast.InitExpr, name string) ?ast.Expr {
|
||||
for field in init.fields {
|
||||
if field.name == name {
|
||||
return field.value
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_v_string_c_expr(expr ast.Expr) {
|
||||
raw := orm_string_value(expr) or { return }
|
||||
g.sb.write_string(c_static_v_string_expr_from_c_literal(c_string_literal_content_to_c(raw)))
|
||||
}
|
||||
|
||||
fn orm_string_value(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.StringLiteral {
|
||||
mut val := strip_literal_quotes(expr.value)
|
||||
if expr.kind == .raw {
|
||||
val = val.replace('\\', '\\\\')
|
||||
} else {
|
||||
val = process_line_continuations(val)
|
||||
}
|
||||
return val
|
||||
}
|
||||
ast.BasicLiteral {
|
||||
if expr.kind == .string {
|
||||
return process_line_continuations(strip_literal_quotes(expr.value))
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_bool_c_expr(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.BasicLiteral {
|
||||
if expr.kind == .key_true || expr.value == 'true' {
|
||||
return 'true'
|
||||
}
|
||||
if expr.kind == .key_false || expr.value == 'false' {
|
||||
return 'false'
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if expr.name == 'true' || expr.name == 'false' {
|
||||
return expr.name
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_number_c_expr(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.BasicLiteral {
|
||||
return sanitize_c_number_literal(expr.value)
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .minus && expr.expr is ast.BasicLiteral {
|
||||
inner := expr.expr as ast.BasicLiteral
|
||||
return '-' + sanitize_c_number_literal(inner.value)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_ident_c_expr(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.Ident {
|
||||
return expr.name
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
if expr.lhs is ast.Ident {
|
||||
left := expr.lhs as ast.Ident
|
||||
return '${left.name}__${expr.rhs.name}'
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// vtest build: !linux && !windows
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
import v2.markused
|
||||
import v2.parser
|
||||
import v2.pref as vpref
|
||||
import v2.token
|
||||
import v2.transformer
|
||||
import v2.types
|
||||
|
||||
fn generate_parallel_worker_c_with_cached_init_for_test(code string, cached_init_calls []string) string {
|
||||
tmp_file := os.join_path(os.temp_dir(), 'v2_parallel_cache_init_test_${os.getpid()}.v')
|
||||
os.write_file(tmp_file, code) or { panic('failed to write temp file') }
|
||||
defer {
|
||||
os.rm(tmp_file) or {}
|
||||
}
|
||||
prefs := &vpref.Preferences{
|
||||
backend: .cleanc
|
||||
no_parallel: false
|
||||
}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut par := parser.Parser.new(prefs)
|
||||
files := par.parse_files([tmp_file], mut file_set)
|
||||
env := types.Environment.new()
|
||||
mut checker := types.Checker.new(prefs, file_set, env)
|
||||
checker.check_files(files)
|
||||
mut trans := transformer.Transformer.new_with_pref(env, prefs)
|
||||
gen_files := trans.transform_files(files)
|
||||
used := markused.mark_used(gen_files, env)
|
||||
mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs)
|
||||
gen.set_used_fn_keys(used)
|
||||
gen.set_cached_init_calls(cached_init_calls)
|
||||
gen.gen_passes_1_to_4()
|
||||
emit_indices := gen.gen_pass5_pre()
|
||||
mut worker := gen.new_pass5_worker(emit_indices, 0)
|
||||
worker.gen_pass5_files(emit_indices)
|
||||
gen.merge_pass5_worker(worker)
|
||||
gen.gen_pass5_post()
|
||||
return gen.gen_finalize()
|
||||
}
|
||||
|
||||
fn test_parallel_worker_preserves_cached_init_calls_in_main() {
|
||||
csrc := generate_parallel_worker_c_with_cached_init_for_test('
|
||||
fn main() {
|
||||
}
|
||||
', [
|
||||
'__v2_cached_init_imports',
|
||||
])
|
||||
assert csrc.contains('__v2_cached_init_imports();')
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
module cleanc
|
||||
|
||||
fn test_pass5_worker_clones_global_type_lookup() {
|
||||
mut g := Gen.new([])
|
||||
g.global_var_types['main__settings'] = 'main__Settings'
|
||||
mut worker := g.new_pass5_worker([], 0)
|
||||
assert worker.global_var_types['main__settings'] == 'main__Settings'
|
||||
g.global_var_types['main__settings'] = 'main__OtherSettings'
|
||||
assert worker.global_var_types['main__settings'] == 'main__Settings'
|
||||
}
|
||||
|
||||
fn test_specialized_receiver_method_index_tracks_ambiguity() {
|
||||
mut g := Gen.new([])
|
||||
g.fn_return_types['Box_T_i32__get'] = 'int'
|
||||
g.remember_specialized_fn_base('Box_T_i32__get')
|
||||
got := g.resolve_specialized_receiver_method('Box', 'get') or { '' }
|
||||
assert got == 'Box_T_i32__get'
|
||||
|
||||
g.fn_return_types['Box_T_string__get'] = 'string'
|
||||
g.remember_specialized_fn_base('Box_T_string__get')
|
||||
if _ := g.resolve_specialized_receiver_method('Box', 'get') {
|
||||
assert false
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
|
||||
// When a file is split across pass-5 workers, a non-owning worker blocks the
|
||||
// file's fns but must still emit its own assigned slice. The owner-scoped bypass
|
||||
// allows exactly the fns owned by the slice's file (explicit_slice_file) and
|
||||
// nothing transitively reached from another file.
|
||||
fn test_explicit_slice_emit_allows_is_scoped_to_owning_file() {
|
||||
mut g := Gen.new([])
|
||||
g.fn_owner_file['fn_foo'] = 3 // foo is owned by file 3
|
||||
g.fn_owner_file['fn_bar'] = 5 // bar is owned by a different file
|
||||
g.blocked_fn_keys['fn_foo'] = true
|
||||
g.blocked_fn_keys['fn_bar'] = true
|
||||
|
||||
// Not in an explicit slice: nothing is unblocked.
|
||||
assert !g.explicit_slice_emit_allows('fn_foo')
|
||||
assert !g.explicit_slice_emit_allows('fn_bar')
|
||||
|
||||
// Emitting file 3's slice: only file 3's fns are unblocked.
|
||||
g.explicit_slice_active = true
|
||||
g.explicit_slice_file = 3
|
||||
assert g.explicit_slice_emit_allows('fn_foo')
|
||||
assert !g.explicit_slice_emit_allows('fn_bar') // owned by file 5 -> stays blocked
|
||||
assert !g.explicit_slice_emit_allows('fn_unknown') // no recorded owner -> blocked
|
||||
|
||||
// Switching to file 5's slice flips which fn is allowed.
|
||||
g.explicit_slice_file = 5
|
||||
assert !g.explicit_slice_emit_allows('fn_foo')
|
||||
assert g.explicit_slice_emit_allows('fn_bar')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// Structure of Arrays (SoA) code generation.
|
||||
//
|
||||
// When a struct is annotated with @[soa], the compiler generates a companion
|
||||
// SoA container struct that stores each field in a separate contiguous array.
|
||||
// This layout provides significantly better cache performance for batch
|
||||
// operations that touch only a subset of fields (common in game math, ECS,
|
||||
// particle systems, physics simulations, etc.).
|
||||
//
|
||||
// Example:
|
||||
// @[soa]
|
||||
// struct Particle {
|
||||
// x f32
|
||||
// y f32
|
||||
// vx f32
|
||||
// vy f32
|
||||
// }
|
||||
//
|
||||
// Generates a companion type `Particle_SOA` with:
|
||||
// struct Particle_SOA {
|
||||
// int len;
|
||||
// int cap;
|
||||
// f32* x;
|
||||
// f32* y;
|
||||
// f32* vx;
|
||||
// f32* vy;
|
||||
// };
|
||||
//
|
||||
// And helper functions:
|
||||
// Particle_SOA Particle_SOA_new(int len, int cap);
|
||||
// void Particle_SOA_push(Particle_SOA* soa, Particle val);
|
||||
// Particle Particle_SOA_get(Particle_SOA soa, int i);
|
||||
// void Particle_SOA_set(Particle_SOA* soa, int i, Particle val);
|
||||
// void Particle_SOA_free(Particle_SOA* soa);
|
||||
module cleanc
|
||||
|
||||
import v2.types
|
||||
|
||||
// gen_soa_companion generates the SoA container struct and helper functions
|
||||
// for a struct annotated with @[soa].
|
||||
fn (mut g Gen) gen_soa_companion(name string, s types.Struct) {
|
||||
soa_name := '${name}_SOA'
|
||||
|
||||
// --- SoA container struct ---
|
||||
g.sb.writeln('// SoA (Structure of Arrays) companion for ${name}')
|
||||
g.sb.writeln('typedef struct {')
|
||||
g.sb.writeln('\tint len;')
|
||||
g.sb.writeln('\tint cap;')
|
||||
for field in s.fields {
|
||||
c_type := g.types_type_to_c(field.typ)
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t${c_type}* ${fname};')
|
||||
}
|
||||
g.sb.writeln('} ${soa_name};')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- new: allocate SoA container ---
|
||||
g.sb.writeln('static inline ${soa_name} ${soa_name}_new(int len, int cap) {')
|
||||
g.sb.writeln('\tif (cap < len) cap = len;')
|
||||
g.sb.writeln('\t${soa_name} soa;')
|
||||
g.sb.writeln('\tsoa.len = len;')
|
||||
g.sb.writeln('\tsoa.cap = cap;')
|
||||
for field in s.fields {
|
||||
c_type := g.types_type_to_c(field.typ)
|
||||
fname := escape_c_keyword(field.name)
|
||||
if g.has_freestanding_hook_capability('alloc') {
|
||||
g.sb.writeln('\tsoa.${fname} = (${c_type}*)v_platform_malloc(cap * sizeof(${c_type}));')
|
||||
g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
|
||||
} else if g.is_freestanding_target() {
|
||||
g.sb.writeln('\tsoa.${fname} = (${c_type}*)${g.c_heap_malloc_call('cap * sizeof(${c_type})')};')
|
||||
g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
|
||||
} else {
|
||||
g.sb.writeln('\tsoa.${fname} = (${c_type}*)calloc(cap, sizeof(${c_type}));')
|
||||
}
|
||||
}
|
||||
g.sb.writeln('\treturn soa;')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- get: retrieve element at index as original struct ---
|
||||
g.sb.writeln('static inline ${name} ${soa_name}_get(${soa_name} soa, int i) {')
|
||||
g.sb.writeln('\treturn (${name}){')
|
||||
for i, field in s.fields {
|
||||
comma := if i < s.fields.len - 1 { ',' } else { '' }
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t\t.${fname} = soa.${fname}[i]${comma}')
|
||||
}
|
||||
g.sb.writeln('\t};')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- set: set element at index from original struct ---
|
||||
g.sb.writeln('static inline void ${soa_name}_set(${soa_name}* soa, int i, ${name} val) {')
|
||||
for field in s.fields {
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\tsoa->${fname}[i] = val.${fname};')
|
||||
}
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- push: append element, growing capacity if needed ---
|
||||
g.sb.writeln('static inline void ${soa_name}_push(${soa_name}* soa, ${name} val) {')
|
||||
g.sb.writeln('\tif (soa->len >= soa->cap) {')
|
||||
g.sb.writeln('\t\tint new_cap = soa->cap < 8 ? 8 : soa->cap * 2;')
|
||||
for field in s.fields {
|
||||
c_type := g.types_type_to_c(field.typ)
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t\tsoa->${fname} = (${c_type}*)${g.c_heap_realloc_call('soa->${fname}',
|
||||
'new_cap * sizeof(${c_type})')};')
|
||||
}
|
||||
g.sb.writeln('\t\tsoa->cap = new_cap;')
|
||||
g.sb.writeln('\t}')
|
||||
for field in s.fields {
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\tsoa->${fname}[soa->len] = val.${fname};')
|
||||
}
|
||||
g.sb.writeln('\tsoa->len++;')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- pop: remove and return last element ---
|
||||
g.sb.writeln('static inline ${name} ${soa_name}_pop(${soa_name}* soa) {')
|
||||
g.sb.writeln('\tif (soa->len == 0) return (${name}){0};')
|
||||
g.sb.writeln('\tsoa->len--;')
|
||||
g.sb.writeln('\treturn (${name}){')
|
||||
for i, field in s.fields {
|
||||
comma := if i < s.fields.len - 1 { ',' } else { '' }
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t\t.${fname} = soa->${fname}[soa->len]${comma}')
|
||||
}
|
||||
g.sb.writeln('\t};')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- free: deallocate all field arrays ---
|
||||
g.sb.writeln('static inline void ${soa_name}_free(${soa_name}* soa) {')
|
||||
for field in s.fields {
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t${g.c_heap_free_call('soa->${fname}')};')
|
||||
}
|
||||
g.sb.writeln('\tsoa->len = 0;')
|
||||
g.sb.writeln('\tsoa->cap = 0;')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import v2.ast
|
||||
import strings
|
||||
|
||||
fn (mut g Gen) gen_string_inter_literal(node ast.StringInterLiteral) {
|
||||
if g.is_freestanding_target() {
|
||||
g.sb.write_string(g.c_freestanding_missing_format_string_expr())
|
||||
return
|
||||
}
|
||||
// Two-pass snprintf: first into a 256-byte stack buffer to measure,
|
||||
// then use it directly or heap-allocate only when it doesn't fit.
|
||||
// This avoids the unconditional heap allocation of asprintf.
|
||||
// Wrapped in GCC compound expression ({ ... })
|
||||
mut fmt_str := strings.new_builder(64)
|
||||
for i, raw_val in node.values {
|
||||
mut val := raw_val
|
||||
// Strip only the outer string delimiters. Using trim_* here can over-strip
|
||||
// escaped quotes in edge chunks like: 'A "${x}"', leaving a trailing `\`.
|
||||
if i == 0 && val.len > 0 && val[0] in [`'`, `"`] {
|
||||
val = val[1..]
|
||||
}
|
||||
if i == node.values.len - 1 && val.len > 0 && val[val.len - 1] in [`'`, `"`] {
|
||||
val = val[..val.len - 1]
|
||||
}
|
||||
escaped := val.replace('%', '%%').replace('\t', '\\t')
|
||||
fmt_str.write_string(escaped)
|
||||
if i < node.inters.len {
|
||||
inter := node.inters[i]
|
||||
fmt_str.write_string(g.get_sprintf_format(inter))
|
||||
}
|
||||
}
|
||||
fmt_lit := c_string_literal_content_to_c(fmt_str.str())
|
||||
|
||||
// Build the argument list string once (used in both snprintf calls).
|
||||
// write_sprintf_arg writes to g.sb, so we temporarily swap it out,
|
||||
// capture the result, and restore it.
|
||||
mut args_sb := strings.new_builder(64)
|
||||
for inter in node.inters {
|
||||
args_sb.write_string(', ')
|
||||
saved := g.sb
|
||||
g.sb = strings.new_builder(64)
|
||||
g.write_sprintf_arg(inter)
|
||||
arg_str := g.sb.str()
|
||||
g.sb = saved
|
||||
args_sb.write_string(arg_str)
|
||||
}
|
||||
args_str := args_sb.str()
|
||||
|
||||
// Emit: try stack buffer first, fall back to malloc only if needed.
|
||||
g.sb.write_string('({ char _sib[256]; int _sil = snprintf(_sib, sizeof(_sib), ${fmt_lit}${args_str}); ')
|
||||
g.sb.write_string('char* _sip; if (_sil < (int)sizeof(_sib)) { _sip = memdup(_sib, _sil + 1); } ')
|
||||
g.sb.write_string('else { _sip = (char*)malloc(_sil + 1); snprintf(_sip, _sil + 1, ${fmt_lit}${args_str}); } ')
|
||||
g.sb.write_string('${c_v_string_expr_from_ptr_len('_sip', '_sil', false)}; })')
|
||||
}
|
||||
|
||||
fn string_inter_needs_selector_type_fallback(expr_type string) bool {
|
||||
return expr_type == '' || expr_type == 'int' || expr_type == 'int_literal'
|
||||
|| expr_type == 'void' || expr_type == 'void*' || expr_type == 'voidptr'
|
||||
}
|
||||
|
||||
fn (mut g Gen) string_inter_expr_type(expr ast.Expr) string {
|
||||
expr_type := g.get_expr_type(expr)
|
||||
if expr is ast.SelectorExpr && string_inter_needs_selector_type_fallback(expr_type) {
|
||||
declared_type := g.selector_declared_field_type(expr)
|
||||
if !string_inter_needs_selector_type_fallback(declared_type) {
|
||||
return declared_type
|
||||
}
|
||||
field_type := g.selector_field_type(expr)
|
||||
if !string_inter_needs_selector_type_fallback(field_type) {
|
||||
return field_type
|
||||
}
|
||||
}
|
||||
return expr_type
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_sprintf_arg(inter ast.StringInter) {
|
||||
expr_type := g.string_inter_expr_type(inter.expr)
|
||||
expr_src := g.expr_to_string(inter.expr)
|
||||
fmt := g.get_sprintf_format(inter)
|
||||
// Keep vararg C types aligned with the emitted format string.
|
||||
// If formatter expects a non-string argument, pass expression as-is.
|
||||
if !fmt.ends_with('s') {
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
return
|
||||
}
|
||||
str_fn := g.get_str_fn_for_type(expr_type) or { '' }
|
||||
// Float types: use V's str() for default formatting ('0.0' not '0.000000')
|
||||
if expr_type in ['f64', 'f32', 'float_literal'] && inter.format == .unformatted {
|
||||
str_name := if expr_type == 'f32' { 'f32__str' } else { 'f64__str' }
|
||||
g.sb.write_string('${str_name}(')
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
g.sb.write_string(').str')
|
||||
return
|
||||
}
|
||||
if expr_type == 'string' {
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('""')
|
||||
return
|
||||
}
|
||||
g.sb.write_string(expr_src)
|
||||
g.sb.write_string('.str')
|
||||
} else if expr_type == 'bool' {
|
||||
g.sb.write_string('(')
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('false')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
g.sb.write_string(' ? "true" : "false")')
|
||||
} else if str_fn != '' {
|
||||
g.sb.write_string('${str_fn}(')
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
g.sb.write_string(').str')
|
||||
} else {
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_sprintf_format(inter ast.StringInter) string {
|
||||
mut fmt := '%'
|
||||
mut width := inter.width
|
||||
mut precision := inter.precision
|
||||
// Extract width/precision from format_expr if not set explicitly
|
||||
if width == 0 && precision == 0 && inter.format_expr !is ast.EmptyExpr {
|
||||
if inter.format_expr is ast.BasicLiteral {
|
||||
val := inter.format_expr.value
|
||||
if val.contains('.') {
|
||||
// ".3" or "0.3" → precision
|
||||
parts := val.split('.')
|
||||
if parts.len == 2 {
|
||||
if parts[0].len > 0 && parts[0] != '0' {
|
||||
width = parts[0].int()
|
||||
}
|
||||
precision = parts[1].int()
|
||||
}
|
||||
} else {
|
||||
// Plain number → width (e.g., "03" for zero-padded)
|
||||
if val.starts_with('0') && val.len > 1 {
|
||||
fmt += '0'
|
||||
}
|
||||
width = val.int()
|
||||
}
|
||||
} else if inter.format_expr is ast.PrefixExpr {
|
||||
// Negative alignment: -10 → left-align with width 10
|
||||
if inter.format_expr.op == .minus && inter.format_expr.expr is ast.BasicLiteral {
|
||||
fmt += '-'
|
||||
width = inter.format_expr.expr.value.int()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Width
|
||||
if width > 0 {
|
||||
fmt += '${width}'
|
||||
}
|
||||
// Precision
|
||||
if precision > 0 {
|
||||
fmt += '.${precision}'
|
||||
}
|
||||
// Format specifier
|
||||
expr_type := g.string_inter_expr_type(inter.expr)
|
||||
if inter.format != .unformatted {
|
||||
match inter.format {
|
||||
.decimal { fmt += 'd' }
|
||||
.float { fmt += 'f' }
|
||||
.hex { fmt += 'x' }
|
||||
.octal { fmt += 'o' }
|
||||
.character { fmt += 'c' }
|
||||
.exponent { fmt += 'e' }
|
||||
.exponent_short { fmt += 'g' }
|
||||
.binary { fmt += 'd' } // binary not supported in printf, fallback to decimal
|
||||
.pointer_address { fmt += 'p' }
|
||||
.string { fmt += 's' }
|
||||
.unformatted { fmt += 'd' }
|
||||
}
|
||||
|
||||
return fmt
|
||||
}
|
||||
if inter.resolved_fmt != '' {
|
||||
if expr_type in ['string', 'bool', 'f32', 'f64', 'float_literal'] {
|
||||
return '%s'
|
||||
}
|
||||
if _ := g.get_str_fn_for_type(expr_type) {
|
||||
return '%s'
|
||||
}
|
||||
return inter.resolved_fmt
|
||||
}
|
||||
// Infer from expression type
|
||||
match expr_type {
|
||||
'string' {
|
||||
return '%s'
|
||||
}
|
||||
'int', 'i8', 'i16', 'i32' {
|
||||
return '%d'
|
||||
}
|
||||
'i64' {
|
||||
return '%lld'
|
||||
}
|
||||
'u8', 'u16', 'u32' {
|
||||
return '%u'
|
||||
}
|
||||
'u64' {
|
||||
return '%llu'
|
||||
}
|
||||
'f32', 'f64', 'float_literal' {
|
||||
// Use %s with V's str() function for default formatting,
|
||||
// since C's %f produces '0.000000' instead of V's '0.0'.
|
||||
return '%s'
|
||||
}
|
||||
'bool' {
|
||||
return '%s'
|
||||
}
|
||||
'rune' {
|
||||
return '%c'
|
||||
}
|
||||
'char' {
|
||||
return '%c'
|
||||
}
|
||||
else {
|
||||
if _ := g.get_str_fn_for_type(expr_type) {
|
||||
return '%s'
|
||||
}
|
||||
return '%d'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get_str_fn_for_type returns the name of the str() function for a type, if one exists.
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,7 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
arr := []f32{len: 4, init: -1e9}
|
||||
println(int(arr[0]))
|
||||
println(int(arr[3]))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
module main
|
||||
|
||||
struct Foo {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := Foo{
|
||||
a: 1
|
||||
b: 2
|
||||
}
|
||||
|
||||
// AssocExpr lowering: Foo{...x b: 3}
|
||||
y := Foo{
|
||||
...x
|
||||
b: 3
|
||||
}
|
||||
println(y.a)
|
||||
println(y.b)
|
||||
|
||||
// &AssocExpr lowering: &Foo{...x a: 9}
|
||||
p := &Foo{
|
||||
...x
|
||||
a: 9
|
||||
}
|
||||
println(p.a)
|
||||
println(p.b)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
a := u32(0x1234)
|
||||
b := u32(0x56)
|
||||
c := u32(0x78)
|
||||
packed := (a >> 3) | (b << 13) | (c << 29)
|
||||
println(packed)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
struct QuaternionKey {
|
||||
values [3]u16
|
||||
}
|
||||
|
||||
fn unpack(key QuaternionKey) u32 {
|
||||
packed := u32(key.values[0]) >> 3 | u32(key.values[1]) << 13 | u32(key.values[2]) << 29
|
||||
return packed
|
||||
}
|
||||
|
||||
fn main() {
|
||||
key := QuaternionKey{
|
||||
values: [u16(0x1234), u16(0x56), u16(0x79)]!
|
||||
}
|
||||
println(unpack(key))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
module main
|
||||
|
||||
@[flag]
|
||||
enum Perms {
|
||||
read
|
||||
write
|
||||
execute
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// .has() and .all()
|
||||
mut p := Perms.read | Perms.write
|
||||
println(p.has(.read))
|
||||
println(p.has(.execute))
|
||||
println(p.all(.read | .write))
|
||||
println(p.all(.read | .execute))
|
||||
|
||||
// .set()
|
||||
p.set(.execute)
|
||||
println(p.has(.execute))
|
||||
println(p.all(.read | .write | .execute))
|
||||
|
||||
// .clear()
|
||||
p.clear(.write)
|
||||
println(p.has(.write))
|
||||
println(p.has(.read))
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
struct Point {
|
||||
x int
|
||||
}
|
||||
|
||||
type Primitive = []int | []string | []Point | bool | int | string | Point
|
||||
|
||||
fn wrap_first[T](values []T) Primitive {
|
||||
if values.len > 0 {
|
||||
first := values[0]
|
||||
return Primitive(first)
|
||||
}
|
||||
return Primitive(false)
|
||||
}
|
||||
|
||||
fn check(value Primitive) {
|
||||
match value {
|
||||
[]int {
|
||||
int_value := wrap_first(value)
|
||||
if int_value is int {
|
||||
println('int:${int_value}')
|
||||
} else {
|
||||
println('int:bad')
|
||||
}
|
||||
}
|
||||
[]string {
|
||||
string_value := wrap_first(value)
|
||||
if string_value is string {
|
||||
println('string:${string_value}')
|
||||
} else {
|
||||
println('string:bad')
|
||||
}
|
||||
}
|
||||
[]Point {
|
||||
point_value := wrap_first(value)
|
||||
if point_value is Point {
|
||||
println('point:${point_value.x}')
|
||||
} else {
|
||||
println('point:bad')
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
ints := []int{len: 1, init: 7}
|
||||
check(Primitive(ints))
|
||||
|
||||
strings := []string{len: 1, init: 'abc'}
|
||||
check(Primitive(strings))
|
||||
|
||||
points := []Point{len: 1, init: Point{
|
||||
x: 3
|
||||
}}
|
||||
check(Primitive(points))
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
module main
|
||||
|
||||
interface Backend {}
|
||||
|
||||
struct MetalBackend {}
|
||||
|
||||
struct NullBackend {}
|
||||
|
||||
fn make_backend() Backend {
|
||||
return MetalBackend{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut backend := make_backend()
|
||||
if mut backend is MetalBackend {
|
||||
println('metal')
|
||||
} else {
|
||||
println('not-metal')
|
||||
}
|
||||
if mut backend is NullBackend {
|
||||
println('null')
|
||||
} else {
|
||||
println('not-null')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
module main
|
||||
|
||||
interface Backend {
|
||||
foo()
|
||||
}
|
||||
|
||||
struct MetalBackend {}
|
||||
|
||||
fn (m MetalBackend) foo() {}
|
||||
|
||||
fn (m MetalBackend) bar() {
|
||||
println('bar')
|
||||
}
|
||||
|
||||
fn make_backend() Backend {
|
||||
return MetalBackend{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut backend := make_backend()
|
||||
if mut backend is MetalBackend {
|
||||
backend.bar()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
module main
|
||||
|
||||
fn make_empty() map[string]int {
|
||||
return {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Empty shorthand map literal `{}` should lower to a correct runtime map constructor.
|
||||
mut m := make_empty()
|
||||
key1 := 'abc'
|
||||
key2 := key1.clone() // Ensure key equality is based on string content, not pointer identity.
|
||||
m[key1] = 123
|
||||
println(m[key2])
|
||||
println(m['missing'])
|
||||
|
||||
// Typed empty map init should also lower to a runtime map constructor.
|
||||
mut m2 := map[string]int{}
|
||||
m2['x'] = 7
|
||||
println(m2['x'])
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
module main
|
||||
|
||||
enum State {
|
||||
idle
|
||||
run
|
||||
}
|
||||
|
||||
fn make_explicit() map[State]int {
|
||||
return {
|
||||
.idle: 10
|
||||
.run: 20
|
||||
}
|
||||
}
|
||||
|
||||
fn make_from_context() map[State]int {
|
||||
return {
|
||||
.idle: 30
|
||||
.run: 40
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := make_explicit()
|
||||
b := make_from_context()
|
||||
println(a[State.idle])
|
||||
println(a[State.run])
|
||||
println(b[State.idle])
|
||||
println(b[State.run])
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
module main
|
||||
|
||||
fn mk() map[string]int {
|
||||
return {
|
||||
'a': 10
|
||||
}
|
||||
}
|
||||
|
||||
fn mk2() map[string]string {
|
||||
return {
|
||||
'x': 'y'
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Map index reads should lower in the transformer even when the map expression is not addressable.
|
||||
println(mk()['a'])
|
||||
println(mk()['missing'])
|
||||
|
||||
// Missing string key should return an empty string (not nil).
|
||||
s := mk2()['missing']
|
||||
println('>${s}<')
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Test runner for cleanc backend regression tests
|
||||
// Run with: ./vnew run vlib/v2_toberemoved/gen/cleanc/tests/run_tests.v
|
||||
// Or: cd vlib/v2_toberemoved/gen/cleanc/tests && v run run_tests.v
|
||||
|
||||
module main
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
t0 := time.now()
|
||||
|
||||
vroot := os.dir(@VEXE)
|
||||
v2_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v')
|
||||
v2_binary := os.join_path(vroot, 'cmd', 'v2', 'v2')
|
||||
tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
|
||||
|
||||
// Build v2 compiler (with v1).
|
||||
println('[*] Building v2...')
|
||||
build_res := os.execute('${@VEXE} -gc none -cc cc ${v2_source} -o ${v2_binary}')
|
||||
if build_res.exit_code != 0 {
|
||||
eprintln('Error: Failed to build v2')
|
||||
eprintln(build_res.output)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Find all test files (*.v except run_tests.v).
|
||||
test_files := os.ls(tests_dir) or {
|
||||
eprintln('Error: Cannot list tests directory')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
mut passed := 0
|
||||
mut failed := 0
|
||||
mut test_count := 0
|
||||
|
||||
for file in test_files {
|
||||
if !file.ends_with('.v') || file == 'run_tests.v' {
|
||||
continue
|
||||
}
|
||||
test_count++
|
||||
test_path := os.join_path(tests_dir, file)
|
||||
test_name := file.replace('.v', '')
|
||||
|
||||
println('\n[*] Testing: ${file}')
|
||||
|
||||
// Run v2 with cleanc backend.
|
||||
v2_output := os.join_path(tests_dir, 'test_${test_name}')
|
||||
v2_cmd := '${v2_binary} -gc none -backend cleanc ${test_path} -o ${v2_output}'
|
||||
v2_res := os.execute(v2_cmd)
|
||||
if v2_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] v2 compilation failed')
|
||||
eprintln(v2_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Run the generated binary.
|
||||
gen_res := os.execute(v2_output)
|
||||
if gen_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] Generated binary exited with code ${gen_res.exit_code}')
|
||||
eprintln(gen_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Run reference compiler (v1).
|
||||
ref_res := os.execute('${@VEXE} -gc none -n -w -enable-globals run ${test_path}')
|
||||
if ref_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] Reference compilation failed')
|
||||
eprintln(ref_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Compare outputs.
|
||||
expected := ref_res.output.trim_space().replace('\r\n', '\n')
|
||||
actual := gen_res.output.trim_space().replace('\r\n', '\n')
|
||||
|
||||
if expected == actual {
|
||||
println(' [PASS] Output matches reference')
|
||||
passed++
|
||||
} else {
|
||||
println(' [FAIL] Output mismatch')
|
||||
println(' Expected:\n${expected}')
|
||||
println(' Got:\n${actual}')
|
||||
failed++
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
os.rm(v2_output) or {}
|
||||
os.rm('${v2_output}.c') or {}
|
||||
}
|
||||
|
||||
println('\n========================================')
|
||||
println('Results: ${passed}/${test_count} tests passed')
|
||||
if failed > 0 {
|
||||
println('${failed} tests FAILED')
|
||||
exit(1)
|
||||
}
|
||||
println('Total time: ${time.since(t0)}')
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module main
|
||||
|
||||
@[soa]
|
||||
struct Vec2 {
|
||||
x f32
|
||||
y f32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Test that the SOA struct definition is generated
|
||||
// The companion type Vec2_SOA should be available
|
||||
println('soa_struct test: ok')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
pub enum X64Abi {
|
||||
sysv
|
||||
windows
|
||||
}
|
||||
|
||||
const x64_no_arg_reg = -1
|
||||
|
||||
fn (abi X64Abi) int_arg_regs() []int {
|
||||
return match abi {
|
||||
.sysv { [int(rdi), int(rsi), int(rdx), int(rcx), int(r8), int(r9)] }
|
||||
.windows { [int(rcx), int(rdx), int(r8), int(r9)] }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) sret_arg_regs() []int {
|
||||
return match abi {
|
||||
.sysv { [int(rsi), int(rdx), int(rcx), int(r8), int(r9)] }
|
||||
.windows { [int(rdx), int(r8), int(r9)] }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) sret_reg() Reg {
|
||||
return match abi {
|
||||
.sysv { rdi }
|
||||
.windows { rcx }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) float_arg_regs() []int {
|
||||
return match abi {
|
||||
.sysv { [0, 1, 2, 3, 4, 5, 6, 7] }
|
||||
.windows { [0, 1, 2, 3] }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) uses_positional_arg_regs() bool {
|
||||
return abi == .windows
|
||||
}
|
||||
|
||||
fn (abi X64Abi) int_arg_reg_at(index int) int {
|
||||
regs := abi.int_arg_regs()
|
||||
if index < 0 || index >= regs.len {
|
||||
return x64_no_arg_reg
|
||||
}
|
||||
return regs[index]
|
||||
}
|
||||
|
||||
fn (abi X64Abi) float_arg_reg_at(index int) int {
|
||||
regs := abi.float_arg_regs()
|
||||
if index < 0 || index >= regs.len {
|
||||
return x64_no_arg_reg
|
||||
}
|
||||
return regs[index]
|
||||
}
|
||||
|
||||
fn (abi X64Abi) int_arg_reg_for_position(arg_idx int) int {
|
||||
return match abi {
|
||||
.sysv { abi.int_arg_reg_at(arg_idx) }
|
||||
.windows { abi.int_arg_reg_at(arg_idx) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) float_arg_reg_for_position(arg_idx int) int {
|
||||
return match abi {
|
||||
.sysv { abi.float_arg_reg_at(arg_idx) }
|
||||
.windows { abi.float_arg_reg_at(arg_idx) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) shadow_space_size() int {
|
||||
return match abi {
|
||||
.sysv { 0 }
|
||||
.windows { 32 }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) stack_arg_offset(position int) int {
|
||||
return match abi {
|
||||
.sysv { 16 + position * 8 }
|
||||
.windows { 48 + (position - 4) * 8 }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) call_stack_arg_offset(position int) int {
|
||||
return match abi {
|
||||
.sysv { position * 8 }
|
||||
.windows { abi.shadow_space_size() + (position - 4) * 8 }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) call_frame_size(stack_slots int) int {
|
||||
base := abi.shadow_space_size() + stack_slots * 8
|
||||
if base % 16 == 0 {
|
||||
return base
|
||||
}
|
||||
return base + (16 - (base % 16))
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
import os
|
||||
|
||||
const coff_image_file_machine_amd64 = u16(0x8664)
|
||||
const coff_image_sym_class_external = u8(2)
|
||||
const coff_image_sym_class_static = u8(3)
|
||||
const coff_image_sym_dtype_function = u16(0x20)
|
||||
|
||||
const coff_image_rel_amd64_rel32 = u16(0x0004)
|
||||
|
||||
const coff_image_scn_cnt_code = u32(0x00000020)
|
||||
const coff_image_scn_cnt_initialized_data = u32(0x00000040)
|
||||
const coff_image_scn_align_8bytes = u32(0x00400000)
|
||||
const coff_image_scn_align_16bytes = u32(0x00500000)
|
||||
const coff_image_scn_mem_execute = u32(0x20000000)
|
||||
const coff_image_scn_mem_read = u32(0x40000000)
|
||||
const coff_image_scn_mem_write = u32(0x80000000)
|
||||
|
||||
pub struct CoffObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
data_data []u8
|
||||
rodata []u8
|
||||
|
||||
symbols []CoffSymbol
|
||||
str_table []u8
|
||||
sym_by_name map[string]int
|
||||
|
||||
text_relocs []CoffRelocation
|
||||
}
|
||||
|
||||
struct CoffSymbol {
|
||||
mut:
|
||||
name string
|
||||
name_off int
|
||||
value u32
|
||||
section i16
|
||||
type_ u16
|
||||
storage_class u8
|
||||
}
|
||||
|
||||
struct CoffRelocation {
|
||||
offset u32
|
||||
sym_idx int
|
||||
type_ u16
|
||||
}
|
||||
|
||||
struct CoffSection {
|
||||
name string
|
||||
data []u8
|
||||
relocs []CoffRelocation
|
||||
characteristics u32
|
||||
}
|
||||
|
||||
pub fn CoffObject.new() &CoffObject {
|
||||
return &CoffObject{}
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) add_symbol(name string, value u64, is_func bool, section u8) int {
|
||||
type_ := if is_func { coff_image_sym_dtype_function } else { u16(0) }
|
||||
storage_class := coff_symbol_storage_class(name, section)
|
||||
if i := c.sym_by_name[name] {
|
||||
mut s := &c.symbols[i]
|
||||
s.value = u32(value)
|
||||
s.section = i16(section)
|
||||
s.type_ = type_
|
||||
s.storage_class = storage_class
|
||||
return i
|
||||
}
|
||||
|
||||
idx := c.symbols.len
|
||||
c.symbols << CoffSymbol{
|
||||
name: name
|
||||
name_off: c.add_string_if_needed(name)
|
||||
value: u32(value)
|
||||
section: i16(section)
|
||||
type_: type_
|
||||
storage_class: storage_class
|
||||
}
|
||||
c.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) add_undefined(name string) int {
|
||||
if i := c.sym_by_name[name] {
|
||||
return i
|
||||
}
|
||||
|
||||
idx := c.symbols.len
|
||||
c.symbols << CoffSymbol{
|
||||
name: name
|
||||
name_off: c.add_string_if_needed(name)
|
||||
value: 0
|
||||
section: 0
|
||||
type_: 0
|
||||
storage_class: coff_image_sym_class_external
|
||||
}
|
||||
c.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
fn coff_symbol_storage_class(name string, section u8) u8 {
|
||||
if section != 0 && name.starts_with('L_') {
|
||||
return coff_image_sym_class_static
|
||||
}
|
||||
return coff_image_sym_class_external
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) add_text_reloc(offset int, sym_idx int, type_ u16) {
|
||||
c.text_relocs << CoffRelocation{
|
||||
offset: u32(offset)
|
||||
sym_idx: sym_idx
|
||||
type_: type_
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c CoffObject) add_string_if_needed(s string) int {
|
||||
if s.len <= 8 {
|
||||
return 0
|
||||
}
|
||||
off := 4 + c.str_table.len
|
||||
c.str_table << s.bytes()
|
||||
c.str_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) write(path string) {
|
||||
sections := [
|
||||
CoffSection{
|
||||
name: '.text'
|
||||
data: c.text_data
|
||||
relocs: c.text_relocs
|
||||
characteristics: coff_image_scn_cnt_code | coff_image_scn_mem_execute | coff_image_scn_mem_read | coff_image_scn_align_16bytes
|
||||
},
|
||||
CoffSection{
|
||||
name: '.rdata'
|
||||
data: c.rodata
|
||||
characteristics: coff_image_scn_cnt_initialized_data | coff_image_scn_mem_read | coff_image_scn_align_8bytes
|
||||
},
|
||||
CoffSection{
|
||||
name: '.data'
|
||||
data: c.data_data
|
||||
characteristics: coff_image_scn_cnt_initialized_data | coff_image_scn_mem_read | coff_image_scn_mem_write | coff_image_scn_align_8bytes
|
||||
},
|
||||
]
|
||||
|
||||
header_size := 20
|
||||
section_header_size := 40
|
||||
reloc_entry_size := 10
|
||||
mut current_offset := header_size + (sections.len * section_header_size)
|
||||
|
||||
mut raw_offsets := []int{len: sections.len}
|
||||
for i, section in sections {
|
||||
if section.data.len > 0 {
|
||||
current_offset = align_int(current_offset, 4)
|
||||
raw_offsets[i] = current_offset
|
||||
current_offset += section.data.len
|
||||
}
|
||||
}
|
||||
|
||||
mut reloc_offsets := []int{len: sections.len}
|
||||
for i, section in sections {
|
||||
if section.relocs.len > 0 {
|
||||
current_offset = align_int(current_offset, 4)
|
||||
reloc_offsets[i] = current_offset
|
||||
current_offset += section.relocs.len * reloc_entry_size
|
||||
}
|
||||
}
|
||||
|
||||
symbol_table_off := align_int(current_offset, 4)
|
||||
string_table_size := 4 + c.str_table.len
|
||||
|
||||
mut buf := []u8{}
|
||||
write_u16_le(mut buf, coff_image_file_machine_amd64)
|
||||
write_u16_le(mut buf, u16(sections.len))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, u32(symbol_table_off))
|
||||
write_u32_le(mut buf, u32(c.symbols.len))
|
||||
write_u16_le(mut buf, 0)
|
||||
write_u16_le(mut buf, 0)
|
||||
|
||||
for i, section in sections {
|
||||
write_fixed_string(mut buf, section.name, 8)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, u32(section.data.len))
|
||||
write_u32_le(mut buf, u32(raw_offsets[i]))
|
||||
write_u32_le(mut buf, u32(reloc_offsets[i]))
|
||||
write_u32_le(mut buf, 0)
|
||||
nrelocs := coff_relocation_count_for_header(section) or { panic(err) }
|
||||
write_u16_le(mut buf, nrelocs)
|
||||
write_u16_le(mut buf, 0)
|
||||
write_u32_le(mut buf, section.characteristics)
|
||||
}
|
||||
|
||||
for i, section in sections {
|
||||
if raw_offsets[i] == 0 {
|
||||
continue
|
||||
}
|
||||
for buf.len < raw_offsets[i] {
|
||||
buf << 0
|
||||
}
|
||||
buf << section.data
|
||||
}
|
||||
|
||||
for i, section in sections {
|
||||
if reloc_offsets[i] == 0 {
|
||||
continue
|
||||
}
|
||||
for buf.len < reloc_offsets[i] {
|
||||
buf << 0
|
||||
}
|
||||
for reloc in section.relocs {
|
||||
write_u32_le(mut buf, reloc.offset)
|
||||
write_u32_le(mut buf, u32(reloc.sym_idx))
|
||||
write_u16_le(mut buf, reloc.type_)
|
||||
}
|
||||
}
|
||||
|
||||
for buf.len < symbol_table_off {
|
||||
buf << 0
|
||||
}
|
||||
for sym in c.symbols {
|
||||
c.write_symbol_name(mut buf, sym)
|
||||
write_u32_le(mut buf, sym.value)
|
||||
write_u16_le(mut buf, u16(sym.section))
|
||||
write_u16_le(mut buf, sym.type_)
|
||||
buf << sym.storage_class
|
||||
buf << 0
|
||||
}
|
||||
|
||||
write_u32_le(mut buf, u32(string_table_size))
|
||||
buf << c.str_table
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn coff_relocation_count_for_header(section CoffSection) !u16 {
|
||||
if section.relocs.len > 0xffff {
|
||||
return error('COFF section ${section.name} has ${section.relocs.len} relocations; extended relocations are not supported')
|
||||
}
|
||||
return u16(section.relocs.len)
|
||||
}
|
||||
|
||||
fn (c CoffObject) write_symbol_name(mut buf []u8, sym CoffSymbol) {
|
||||
if sym.name.len <= 8 {
|
||||
write_fixed_string(mut buf, sym.name, 8)
|
||||
return
|
||||
}
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, u32(sym.name_off))
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
const x64_backend_feature_prefix = 'backend feature: '
|
||||
pub const x64_backend_limitation_hint = 'x64: native backend limitation: retry with the C backend or reduce the source to supported x64 features'
|
||||
|
||||
const x64_minimal_unsupported_crt_stdio_symbols = ['stdin', 'stdout', 'stderr', 'printf', 'fprintf',
|
||||
'dprintf', 'sprintf', 'snprintf', 'wprintf', 'puts', 'fputs', 'setvbuf', 'fflush', 'fopen',
|
||||
'_wfopen', 'fdopen', 'freopen', '_wfreopen', 'fclose', 'pclose', '_pclose', 'fread', 'fwrite',
|
||||
'fgets', 'getc', 'feof', 'ferror', 'fseek', 'ftell', 'rewind', 'fileno', '_fileno',
|
||||
'_get_osfhandle', '_open_osfhandle', 'fgetpos']
|
||||
|
||||
const x64_unsupported_captured_fn_literal_symbol = 'v2_unsupported_captured_fn_literal'
|
||||
const x64_missing_v_runtime_helpers = ['builtin__Map_string_int__keys']
|
||||
|
||||
fn x64_normalize_backend_feature(feature string) string {
|
||||
if feature.starts_with(x64_backend_feature_prefix) {
|
||||
return feature[x64_backend_feature_prefix.len..]
|
||||
}
|
||||
return feature
|
||||
}
|
||||
|
||||
fn x64_unsupported_backend_feature_message(feature string) string {
|
||||
return 'x64: unsupported backend feature: ${x64_normalize_backend_feature(feature)}'
|
||||
}
|
||||
|
||||
fn x64_unsupported(message string) {
|
||||
x64_abort_with_diagnostic(x64_unsupported_backend_feature_message(message))
|
||||
}
|
||||
|
||||
fn x64_abort_with_diagnostic(message string) {
|
||||
eprintln(message)
|
||||
eprintln(x64_backend_limitation_hint)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
fn x64_linker_name(format ObjectFormat) string {
|
||||
return match format {
|
||||
.elf { 'ELF linker' }
|
||||
.macho { 'Mach-O linker' }
|
||||
.coff { 'PE linker' }
|
||||
}
|
||||
}
|
||||
|
||||
fn x64_unresolved_external_symbol_message(format ObjectFormat, name string, context string) string {
|
||||
linker_name := x64_linker_name(format)
|
||||
if format == .coff && name in x64_minimal_unsupported_crt_stdio_symbols {
|
||||
return x64_unsupported_backend_feature_message('${linker_name} cannot resolve C stdio/file-descriptor symbol `${name}`: Windows x64 native backend uses Kernel32 handles, not C FILE/stdio calls; ${context}')
|
||||
}
|
||||
if name == x64_unsupported_captured_fn_literal_symbol {
|
||||
return x64_unsupported_backend_feature_message('native x64 cannot lower captured function literal: closure environments are not implemented yet; ${context}')
|
||||
}
|
||||
if name in x64_missing_v_runtime_helpers {
|
||||
return x64_unsupported_backend_feature_message('${linker_name} cannot resolve V runtime helper `${name}`: native x64 backend does not implement this feature for this target yet; ${context}')
|
||||
}
|
||||
return x64_unsupported_backend_feature_message('${linker_name} cannot resolve external symbol `${name}` yet; ${context}')
|
||||
}
|
||||
|
||||
pub fn unsupported_external_symbol_message_for_name(format ObjectFormat, raw_name string, context string) ?string {
|
||||
name := x64_normalize_external_symbol_name(format, raw_name)
|
||||
if x64_symbol_needs_backend_runtime_support(name) {
|
||||
return x64_unresolved_external_symbol_message(format, name, context)
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn x64_normalize_external_symbol_name(format ObjectFormat, name string) string {
|
||||
if format == .macho && name.starts_with('_') {
|
||||
return name[1..]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn x64_symbol_needs_backend_runtime_support(name string) bool {
|
||||
return name == x64_unsupported_captured_fn_literal_symbol
|
||||
|| name in x64_missing_v_runtime_helpers
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
import os
|
||||
|
||||
// ELF64 Constants
|
||||
const ei_mag0 = 0x7f
|
||||
const ei_mag1 = 0x45 // 'E'
|
||||
const ei_mag2 = 0x4c // 'L'
|
||||
const ei_mag3 = 0x46 // 'F'
|
||||
|
||||
const elfclass64 = 2
|
||||
const elfdata2lsb = 1
|
||||
const ev_current = 1
|
||||
const et_rel = 1 // Relocatable file
|
||||
const et_exec = 2 // Executable file
|
||||
const em_x86_64 = 62
|
||||
|
||||
const pt_load = 1
|
||||
|
||||
const pf_x = 0x1
|
||||
const pf_w = 0x2
|
||||
const pf_r = 0x4
|
||||
|
||||
const sht_progbits = 1
|
||||
const sht_symtab = 2
|
||||
const sht_strtab = 3
|
||||
const sht_rela = 4
|
||||
const sht_nobits = 8
|
||||
|
||||
const shf_write = 0x1
|
||||
const shf_alloc = 0x2
|
||||
const shf_execinstr = 0x4
|
||||
|
||||
// Relocation Types for x86_64
|
||||
const r_x86_64_64 = 1
|
||||
const r_x86_64_pc32 = 2
|
||||
const r_x86_64_plt32 = 4
|
||||
|
||||
pub struct ElfObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
data_data []u8
|
||||
rodata []u8
|
||||
|
||||
// Symbol Table
|
||||
symbols []ElfSymbol
|
||||
str_table []u8
|
||||
|
||||
// Relocations
|
||||
text_relocs []ElfRela
|
||||
|
||||
// Section Names
|
||||
shstr_table []u8
|
||||
}
|
||||
|
||||
struct ElfSymbol {
|
||||
pub mut:
|
||||
name string
|
||||
name_idx int
|
||||
info u8
|
||||
shndx u16
|
||||
value u64
|
||||
size u64
|
||||
}
|
||||
|
||||
struct ElfRela {
|
||||
offset u64
|
||||
info u64
|
||||
addend i64
|
||||
}
|
||||
|
||||
pub fn ElfObject.new() &ElfObject {
|
||||
mut e := &ElfObject{
|
||||
str_table: [u8(0)] // Starts with null byte
|
||||
shstr_table: [u8(0)]
|
||||
}
|
||||
// Add null symbol
|
||||
e.symbols << ElfSymbol{
|
||||
name: ''
|
||||
shndx: 0
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) add_symbol(name string, value u64, is_func bool, shndx u16) int {
|
||||
// STB_GLOBAL (1) << 4 | STT_FUNC (2) or STT_OBJECT (1) or STT_NOTYPE (0)
|
||||
type_ := if is_func { u8(2) } else { u8(1) } // Func : Object
|
||||
bind := u8(1) // Global
|
||||
info := (bind << 4) | type_
|
||||
|
||||
for i := 1; i < e.symbols.len; i++ {
|
||||
if e.symbols[i].name != name {
|
||||
continue
|
||||
}
|
||||
mut s := &e.symbols[i]
|
||||
s.info = info
|
||||
s.shndx = shndx
|
||||
s.value = value
|
||||
return i
|
||||
}
|
||||
|
||||
idx := e.symbols.len
|
||||
name_off := e.add_string(name)
|
||||
|
||||
e.symbols << ElfSymbol{
|
||||
name: name
|
||||
name_idx: name_off
|
||||
info: info
|
||||
shndx: shndx
|
||||
value: value
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) add_undefined(name string) int {
|
||||
for i := 1; i < e.symbols.len; i++ {
|
||||
if e.symbols[i].name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
idx := e.symbols.len
|
||||
name_off := e.add_string(name)
|
||||
// Bind Global (1), Type NoType (0)
|
||||
info := u8(0x10)
|
||||
|
||||
e.symbols << ElfSymbol{
|
||||
name: name
|
||||
name_idx: name_off
|
||||
info: info
|
||||
shndx: 0 // Undefined
|
||||
value: 0
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) add_text_reloc(offset u64, sym_idx int, type_ int, addend i64) {
|
||||
// info = (sym_idx << 32) | type
|
||||
info := (u64(sym_idx) << 32) | u64(type_)
|
||||
e.text_relocs << ElfRela{
|
||||
offset: offset
|
||||
info: info
|
||||
addend: addend
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut e ElfObject) add_string(s string) int {
|
||||
off := e.str_table.len
|
||||
e.str_table << s.bytes()
|
||||
e.str_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
fn (mut e ElfObject) add_sh_string(s string) int {
|
||||
off := e.shstr_table.len
|
||||
e.shstr_table << s.bytes()
|
||||
e.shstr_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) write(path string) {
|
||||
mut buf := []u8{}
|
||||
|
||||
// --- 1. Prepare Sections ---
|
||||
// Indices:
|
||||
// 0: Null
|
||||
// 1: .text
|
||||
// 2: .data
|
||||
// 3: .rodata (strings)
|
||||
// 4: .symtab
|
||||
// 5: .strtab
|
||||
// 6: .rela.text
|
||||
// 7: .shstrtab
|
||||
|
||||
// Prepare section names in .shstrtab
|
||||
off_text_name := e.add_sh_string('.text')
|
||||
off_data_name := e.add_sh_string('.data')
|
||||
off_rodata_name := e.add_sh_string('.rodata')
|
||||
off_symtab_name := e.add_sh_string('.symtab')
|
||||
off_strtab_name := e.add_sh_string('.strtab')
|
||||
off_rela_text_name := e.add_sh_string('.rela.text')
|
||||
off_shstrtab_name := e.add_sh_string('.shstrtab')
|
||||
|
||||
// Calculate Offsets
|
||||
ehdr_size := 64
|
||||
shdr_entry_size := 64
|
||||
num_sections := 8
|
||||
|
||||
mut current_offset := ehdr_size
|
||||
|
||||
// .text (Align 16)
|
||||
if current_offset % 16 != 0 {
|
||||
current_offset += (16 - (current_offset % 16))
|
||||
}
|
||||
off_text := current_offset
|
||||
current_offset += e.text_data.len
|
||||
|
||||
// .data (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_data := current_offset
|
||||
current_offset += e.data_data.len
|
||||
|
||||
// .rodata (Align 4)
|
||||
if current_offset % 4 != 0 {
|
||||
current_offset += (4 - (current_offset % 4))
|
||||
}
|
||||
off_rodata := current_offset
|
||||
current_offset += e.rodata.len
|
||||
|
||||
// .symtab (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_symtab := current_offset
|
||||
size_symtab := e.symbols.len * 24
|
||||
current_offset += size_symtab
|
||||
|
||||
// .strtab (Align 1)
|
||||
off_strtab := current_offset
|
||||
current_offset += e.str_table.len
|
||||
|
||||
// .rela.text (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_rela_text := current_offset
|
||||
size_rela_text := e.text_relocs.len * 24
|
||||
current_offset += size_rela_text
|
||||
|
||||
// .shstrtab (Align 1)
|
||||
off_shstrtab := current_offset
|
||||
current_offset += e.shstr_table.len
|
||||
|
||||
// Section Headers (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_shdrs := current_offset
|
||||
|
||||
// --- 2. Write ELF Header ---
|
||||
buf << u8(ei_mag0)
|
||||
buf << u8(ei_mag1)
|
||||
buf << u8(ei_mag2)
|
||||
buf << u8(ei_mag3)
|
||||
buf << u8(elfclass64)
|
||||
buf << u8(elfdata2lsb)
|
||||
buf << u8(ev_current)
|
||||
buf << u8(0) // ABI System V
|
||||
buf << u8(0) // ABI Version
|
||||
for _ in 0 .. 7 {
|
||||
buf << 0
|
||||
} // Pad
|
||||
|
||||
write_u16_le(mut buf, et_rel)
|
||||
write_u16_le(mut buf, em_x86_64)
|
||||
write_u32_le(mut buf, ev_current)
|
||||
write_u64_le(mut buf, 0) // Entry
|
||||
write_u64_le(mut buf, 0) // Phdr off
|
||||
write_u64_le(mut buf, u64(off_shdrs))
|
||||
write_u32_le(mut buf, 0) // Flags
|
||||
write_u16_le(mut buf, u16(ehdr_size))
|
||||
write_u16_le(mut buf, 0) // Phdr entry size
|
||||
write_u16_le(mut buf, 0) // Phdr num
|
||||
write_u16_le(mut buf, u16(shdr_entry_size))
|
||||
write_u16_le(mut buf, u16(num_sections))
|
||||
write_u16_le(mut buf, 7) // Shstrndx
|
||||
|
||||
// --- 3. Write Data with Padding ---
|
||||
|
||||
// Pad to Text
|
||||
for buf.len < off_text {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.text_data
|
||||
|
||||
// Pad to Data
|
||||
for buf.len < off_data {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.data_data
|
||||
|
||||
// Pad to Rodata
|
||||
for buf.len < off_rodata {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.rodata
|
||||
|
||||
// Pad to Symtab
|
||||
for buf.len < off_symtab {
|
||||
buf << 0
|
||||
}
|
||||
for sym in e.symbols {
|
||||
write_u32_le(mut buf, u32(sym.name_idx))
|
||||
buf << sym.info
|
||||
buf << 0 // other
|
||||
write_u16_le(mut buf, sym.shndx)
|
||||
write_u64_le(mut buf, sym.value)
|
||||
write_u64_le(mut buf, sym.size)
|
||||
}
|
||||
|
||||
// Strtab
|
||||
for buf.len < off_strtab {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.str_table
|
||||
|
||||
// Rela Text
|
||||
for buf.len < off_rela_text {
|
||||
buf << 0
|
||||
}
|
||||
for r in e.text_relocs {
|
||||
write_u64_le(mut buf, r.offset)
|
||||
write_u64_le(mut buf, r.info)
|
||||
write_u64_le(mut buf, u64(r.addend))
|
||||
}
|
||||
|
||||
// Shstrtab
|
||||
for buf.len < off_shstrtab {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.shstr_table
|
||||
|
||||
// --- 4. Write Section Headers ---
|
||||
for buf.len < off_shdrs {
|
||||
buf << 0
|
||||
}
|
||||
|
||||
// 0: Null
|
||||
write_shdr(mut buf, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
// 1: .text
|
||||
write_shdr(mut buf, u32(off_text_name), sht_progbits, shf_alloc | shf_execinstr, u64(off_text),
|
||||
u64(e.text_data.len), 0, 0, 16, 0)
|
||||
|
||||
// 2: .data
|
||||
write_shdr(mut buf, u32(off_data_name), sht_progbits, shf_alloc | shf_write, u64(off_data),
|
||||
u64(e.data_data.len), 0, 0, 8, 0)
|
||||
|
||||
// 3: .rodata
|
||||
write_shdr(mut buf, u32(off_rodata_name), sht_progbits, shf_alloc, u64(off_rodata),
|
||||
u64(e.rodata.len), 0, 0, 4, 0)
|
||||
|
||||
// 4: .symtab (EntSize = 24)
|
||||
mut first_global := 1
|
||||
for i, s in e.symbols {
|
||||
if (s.info >> 4) == 1 { // STB_GLOBAL
|
||||
first_global = i
|
||||
break
|
||||
}
|
||||
}
|
||||
write_shdr(mut buf, u32(off_symtab_name), sht_symtab, 0, u64(off_symtab), u64(size_symtab), 5,
|
||||
u32(first_global), 8, 24)
|
||||
|
||||
// 5: .strtab
|
||||
write_shdr(mut buf, u32(off_strtab_name), sht_strtab, 0, u64(off_strtab), u64(e.str_table.len),
|
||||
0, 0, 1, 0)
|
||||
|
||||
// 6: .rela.text (EntSize = 24)
|
||||
write_shdr(mut buf, u32(off_rela_text_name), sht_rela, 0, u64(off_rela_text),
|
||||
u64(size_rela_text), 4, 1, 8, 24)
|
||||
|
||||
// 7: .shstrtab
|
||||
write_shdr(mut buf, u32(off_shstrtab_name), sht_strtab, 0, u64(off_shstrtab),
|
||||
u64(e.shstr_table.len), 0, 0, 1, 0)
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn write_shdr(mut b []u8, name u32, type_ u32, flags u64, off u64, size u64, link u32, info u32, align u64, entsize u64) {
|
||||
write_u32_le(mut b, name)
|
||||
write_u32_le(mut b, type_)
|
||||
write_u64_le(mut b, flags)
|
||||
write_u64_le(mut b, 0) // Addr
|
||||
write_u64_le(mut b, off)
|
||||
write_u64_le(mut b, size)
|
||||
write_u32_le(mut b, link)
|
||||
write_u32_le(mut b, info)
|
||||
write_u64_le(mut b, align)
|
||||
write_u64_le(mut b, entsize)
|
||||
}
|
||||
|
||||
fn write_u32_le(mut b []u8, v u32) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
b << u8(v >> 16)
|
||||
b << u8(v >> 24)
|
||||
}
|
||||
|
||||
fn write_u64_le(mut b []u8, v u64) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
b << u8(v >> 16)
|
||||
b << u8(v >> 24)
|
||||
b << u8(v >> 32)
|
||||
b << u8(v >> 40)
|
||||
b << u8(v >> 48)
|
||||
b << u8(v >> 56)
|
||||
}
|
||||
|
||||
fn write_u16_le(mut b []u8, v u16) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
import os
|
||||
|
||||
const macho_mh_magic_64 = u32(0xfeedfacf)
|
||||
const macho_cpu_type_x86_64 = 0x01000007
|
||||
const macho_cpu_subtype_x86_64_all = 3
|
||||
const macho_mh_object = 1
|
||||
const macho_lc_segment_64 = 0x19
|
||||
const macho_lc_symtab = 0x2
|
||||
|
||||
const x86_64_reloc_signed = 1
|
||||
const x86_64_reloc_branch = 2
|
||||
const x86_64_reloc_got_load = 3
|
||||
const x86_64_reloc_got = 4
|
||||
|
||||
pub struct MachOObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
data_data []u8
|
||||
rodata []u8
|
||||
|
||||
relocs []MachORelocationInfo
|
||||
symbols []MachOSymbol
|
||||
str_table []u8
|
||||
sym_by_name map[string]int
|
||||
}
|
||||
|
||||
struct MachORelocationInfo {
|
||||
addr int
|
||||
sym_idx int
|
||||
pcrel bool
|
||||
length int
|
||||
extern bool
|
||||
type_ int
|
||||
}
|
||||
|
||||
struct MachOSymbol {
|
||||
mut:
|
||||
name string
|
||||
type_ u8
|
||||
sect u8
|
||||
desc u16
|
||||
value u64
|
||||
name_off int
|
||||
}
|
||||
|
||||
pub fn MachOObject.new() &MachOObject {
|
||||
return &MachOObject{
|
||||
str_table: [u8(0)]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_symbol(name string, value u64, is_ext bool, sect u8) int {
|
||||
typ := if is_ext { u8(0x0f) } else { u8(0x0e) }
|
||||
if i := m.sym_by_name[name] {
|
||||
mut s := &m.symbols[i]
|
||||
s.type_ = typ
|
||||
s.sect = sect
|
||||
s.value = value
|
||||
return i
|
||||
}
|
||||
|
||||
idx := m.symbols.len
|
||||
name_off := m.add_string(name)
|
||||
m.symbols << MachOSymbol{
|
||||
name: name
|
||||
type_: typ
|
||||
sect: sect
|
||||
desc: 0
|
||||
value: value
|
||||
name_off: name_off
|
||||
}
|
||||
m.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_undefined(name string) int {
|
||||
if i := m.sym_by_name[name] {
|
||||
return i
|
||||
}
|
||||
|
||||
idx := m.symbols.len
|
||||
name_off := m.add_string(name)
|
||||
m.symbols << MachOSymbol{
|
||||
name: name
|
||||
type_: 0x01 // N_UNDF | N_EXT
|
||||
sect: 0
|
||||
desc: 0
|
||||
value: 0
|
||||
name_off: name_off
|
||||
}
|
||||
m.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_reloc(addr int, sym_idx int, typ int, pcrel bool, length int) {
|
||||
m.relocs << MachORelocationInfo{
|
||||
addr: addr
|
||||
sym_idx: sym_idx
|
||||
type_: typ
|
||||
pcrel: pcrel
|
||||
length: length
|
||||
extern: true
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut m MachOObject) add_string(s string) int {
|
||||
off := m.str_table.len
|
||||
m.str_table << s.bytes()
|
||||
m.str_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) write(path string) {
|
||||
mut buf := []u8{}
|
||||
|
||||
n_sects := 3
|
||||
header_size := 32
|
||||
seg_cmd_size := 72 + (80 * n_sects)
|
||||
symtab_cmd_size := 24
|
||||
load_cmds_size := seg_cmd_size + symtab_cmd_size
|
||||
|
||||
text_off := align_int(header_size + load_cmds_size, 16)
|
||||
text_len := m.text_data.len
|
||||
rodata_off := align_int(text_off + text_len, 8)
|
||||
rodata_len := m.rodata.len
|
||||
data_off := align_int(rodata_off + rodata_len, 8)
|
||||
data_len := m.data_data.len
|
||||
|
||||
text_addr := 0
|
||||
rodata_addr := align_int(text_addr + text_len, 8)
|
||||
data_addr := align_int(rodata_addr + rodata_len, 8)
|
||||
total_size := data_addr + data_len
|
||||
|
||||
reloc_off := align_int(data_off + data_len, 8)
|
||||
sym_off := align_int(reloc_off + (m.relocs.len * 8), 8)
|
||||
sym_len := m.symbols.len * 16
|
||||
str_off := sym_off + sym_len
|
||||
str_size := m.str_table.len
|
||||
|
||||
write_u32_le(mut buf, macho_mh_magic_64)
|
||||
write_u32_le(mut buf, u32(macho_cpu_type_x86_64))
|
||||
write_u32_le(mut buf, u32(macho_cpu_subtype_x86_64_all))
|
||||
write_u32_le(mut buf, macho_mh_object)
|
||||
write_u32_le(mut buf, 2)
|
||||
write_u32_le(mut buf, u32(load_cmds_size))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_u32_le(mut buf, macho_lc_segment_64)
|
||||
write_u32_le(mut buf, u32(seg_cmd_size))
|
||||
write_fixed_string(mut buf, '', 16)
|
||||
write_u64_le(mut buf, 0)
|
||||
write_u64_le(mut buf, u64(total_size))
|
||||
write_u64_le(mut buf, u64(text_off))
|
||||
write_u64_le(mut buf, u64(data_off + data_len - text_off))
|
||||
write_u32_le(mut buf, 7)
|
||||
write_u32_le(mut buf, 7)
|
||||
write_u32_le(mut buf, u32(n_sects))
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_fixed_string(mut buf, '__text', 16)
|
||||
write_fixed_string(mut buf, '__TEXT', 16)
|
||||
write_u64_le(mut buf, u64(text_addr))
|
||||
write_u64_le(mut buf, u64(text_len))
|
||||
write_u32_le(mut buf, u32(text_off))
|
||||
write_u32_le(mut buf, 4)
|
||||
write_u32_le(mut buf, u32(reloc_off))
|
||||
write_u32_le(mut buf, u32(m.relocs.len))
|
||||
write_u32_le(mut buf, u32(0x80000400))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_fixed_string(mut buf, '__const', 16)
|
||||
write_fixed_string(mut buf, '__TEXT', 16)
|
||||
write_u64_le(mut buf, u64(rodata_addr))
|
||||
write_u64_le(mut buf, u64(rodata_len))
|
||||
write_u32_le(mut buf, u32(rodata_off))
|
||||
write_u32_le(mut buf, 3)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_fixed_string(mut buf, '__data', 16)
|
||||
write_fixed_string(mut buf, '__DATA', 16)
|
||||
write_u64_le(mut buf, u64(data_addr))
|
||||
write_u64_le(mut buf, u64(data_len))
|
||||
write_u32_le(mut buf, u32(data_off))
|
||||
write_u32_le(mut buf, 3)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_u32_le(mut buf, macho_lc_symtab)
|
||||
write_u32_le(mut buf, u32(symtab_cmd_size))
|
||||
write_u32_le(mut buf, u32(sym_off))
|
||||
write_u32_le(mut buf, u32(m.symbols.len))
|
||||
write_u32_le(mut buf, u32(str_off))
|
||||
write_u32_le(mut buf, u32(str_size))
|
||||
|
||||
for buf.len < text_off {
|
||||
buf << 0
|
||||
}
|
||||
buf << m.text_data
|
||||
for buf.len < rodata_off {
|
||||
buf << 0
|
||||
}
|
||||
buf << m.rodata
|
||||
for buf.len < data_off {
|
||||
buf << 0
|
||||
}
|
||||
buf << m.data_data
|
||||
|
||||
for buf.len < reloc_off {
|
||||
buf << 0
|
||||
}
|
||||
for r in m.relocs {
|
||||
write_u32_le(mut buf, u32(r.addr))
|
||||
mut info := u32(r.sym_idx)
|
||||
if r.pcrel {
|
||||
info |= 1 << 24
|
||||
}
|
||||
info |= u32(r.length) << 25
|
||||
if r.extern {
|
||||
info |= 1 << 27
|
||||
}
|
||||
info |= u32(r.type_) << 28
|
||||
write_u32_le(mut buf, info)
|
||||
}
|
||||
|
||||
for buf.len < sym_off {
|
||||
buf << 0
|
||||
}
|
||||
for s in m.symbols {
|
||||
write_u32_le(mut buf, u32(s.name_off))
|
||||
buf << s.type_
|
||||
buf << s.sect
|
||||
write_u16_le(mut buf, s.desc)
|
||||
write_u64_le(mut buf, m.symbol_value(s, rodata_addr, data_addr))
|
||||
}
|
||||
|
||||
buf << m.str_table
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn (m MachOObject) symbol_value(s MachOSymbol, rodata_addr int, data_addr int) u64 {
|
||||
return match s.sect {
|
||||
1 { s.value }
|
||||
2 { u64(rodata_addr) + s.value }
|
||||
3 { u64(data_addr) + s.value }
|
||||
else { s.value }
|
||||
}
|
||||
}
|
||||
|
||||
fn align_int(value int, alignment int) int {
|
||||
if alignment <= 1 || value % alignment == 0 {
|
||||
return value
|
||||
}
|
||||
return value + (alignment - (value % alignment))
|
||||
}
|
||||
|
||||
fn write_fixed_string(mut b []u8, s string, len int) {
|
||||
bytes := s.bytes()
|
||||
for i in 0 .. len {
|
||||
if i < bytes.len {
|
||||
b << bytes[i]
|
||||
} else {
|
||||
b << 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
import encoding.binary
|
||||
|
||||
pub const macos_tiny_not_eligible_prefix = 'macOS tiny object is not eligible: '
|
||||
|
||||
const macos_tiny_builtin_string_plus_symbol = '_builtin__string__+'
|
||||
const macos_tiny_builtin_i64_str_symbol = '_builtin__i64__str'
|
||||
const macos_tiny_malloc_symbol = '_malloc'
|
||||
const macos_tiny_exit_symbol = '_exit'
|
||||
|
||||
struct MachOTextRange {
|
||||
name string
|
||||
start u64
|
||||
end u64
|
||||
}
|
||||
|
||||
struct MachODataRange {
|
||||
section ObjectSection
|
||||
start u64
|
||||
end u64
|
||||
}
|
||||
|
||||
struct MachOTinyReachable {
|
||||
mut:
|
||||
names []string
|
||||
data []MachODataRange
|
||||
undefined map[string]bool
|
||||
}
|
||||
|
||||
struct MachOTinyObjectWriter {
|
||||
macho &MachOObject
|
||||
}
|
||||
|
||||
struct MachOTinyRuntimeReloc {
|
||||
addr int
|
||||
symbol string
|
||||
}
|
||||
|
||||
pub fn (mut g Gen) write_macos_tiny_object(path string) ! {
|
||||
if g.obj_format != .macho {
|
||||
return error('macOS tiny object writing requires Mach-O object output')
|
||||
}
|
||||
if g.abi != .sysv {
|
||||
return error('macOS tiny object writing requires SysV x64 code generation')
|
||||
}
|
||||
mut writer := MachOTinyObjectWriter{
|
||||
macho: g.macho
|
||||
}
|
||||
writer.write(path)!
|
||||
}
|
||||
|
||||
fn (mut w MachOTinyObjectWriter) write(path string) ! {
|
||||
mut reachable := w.collect_reachable()!
|
||||
mut out := MachOObject.new()
|
||||
mut symbol_indices := map[string]int{}
|
||||
mut func_offsets := map[string]u64{}
|
||||
mut runtime_symbols := []string{}
|
||||
mut runtime_relocs := []MachOTinyRuntimeReloc{}
|
||||
|
||||
for name in reachable.names {
|
||||
range := w.text_range(name)!
|
||||
func_offsets[name] = u64(out.text_data.len)
|
||||
out.text_data << w.macho.text_data[int(range.start)..int(range.end)]
|
||||
}
|
||||
macho_tiny_add_runtime_helpers(mut out, mut reachable, mut func_offsets, mut runtime_symbols, mut
|
||||
runtime_relocs)
|
||||
|
||||
mut data_offsets := map[string]u64{}
|
||||
w.copy_data_ranges(reachable.data, .rodata, mut out.rodata, mut data_offsets)!
|
||||
w.copy_data_ranges(reachable.data, .data, mut out.data_data, mut data_offsets)!
|
||||
|
||||
for name in reachable.names {
|
||||
symbol_indices[name] = out.add_symbol(name, func_offsets[name],
|
||||
macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(.text))
|
||||
}
|
||||
for name in runtime_symbols {
|
||||
symbol_indices[name] = out.add_symbol(name, func_offsets[name],
|
||||
macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(.text))
|
||||
}
|
||||
mut data_names := data_offsets.keys()
|
||||
data_names.sort()
|
||||
for name in data_names {
|
||||
orig := w.symbol_by_name(name)!
|
||||
section := w.symbol_object_section(orig)!
|
||||
symbol_indices[name] = out.add_symbol(name, data_offsets[name],
|
||||
macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(section))
|
||||
}
|
||||
mut undefined_names := reachable.undefined.keys()
|
||||
undefined_names.sort()
|
||||
for name in undefined_names {
|
||||
symbol_indices[name] = out.add_undefined(name)
|
||||
}
|
||||
for name in reachable.names {
|
||||
range := w.text_range(name)!
|
||||
new_base := func_offsets[name]
|
||||
for reloc in w.macho.relocs {
|
||||
if reloc.addr < int(range.start) || reloc.addr >= int(range.end) {
|
||||
continue
|
||||
}
|
||||
sym := w.reloc_symbol(reloc)!
|
||||
sym_idx := symbol_indices[sym.name] or {
|
||||
return macos_tiny_not_eligible('symbol `${sym.name}` is not resolved by the tiny object')
|
||||
}
|
||||
out_reloc_addr := int(new_base + u64(reloc.addr - int(range.start)))
|
||||
if macho_tiny_resolve_relocation_locally(sym.name, reloc) {
|
||||
macho_tiny_patch_rel32_local(mut out.text_data, out_reloc_addr,
|
||||
int(func_offsets[sym.name]))
|
||||
continue
|
||||
}
|
||||
out.add_reloc(out_reloc_addr, sym_idx, reloc.type_, reloc.pcrel, reloc.length)
|
||||
}
|
||||
}
|
||||
for reloc in runtime_relocs {
|
||||
sym_idx := symbol_indices[reloc.symbol] or {
|
||||
return macos_tiny_not_eligible('runtime helper symbol `${reloc.symbol}` is not resolved by the tiny object')
|
||||
}
|
||||
out.add_reloc(reloc.addr, sym_idx, x86_64_reloc_branch, true, 2)
|
||||
}
|
||||
|
||||
out.write(path)
|
||||
}
|
||||
|
||||
fn (mut w MachOTinyObjectWriter) collect_reachable() !MachOTinyReachable {
|
||||
_ = w.text_range('_main') or { return macos_tiny_not_eligible('missing _main symbol') }
|
||||
mut selected := []string{}
|
||||
mut seen := map[string]bool{}
|
||||
mut queue := ['_main']
|
||||
mut selected_data := []MachODataRange{}
|
||||
mut undefined := map[string]bool{}
|
||||
for queue.len > 0 {
|
||||
name := queue[0]
|
||||
queue.delete(0)
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
if macho_tiny_is_module_init_symbol(name) {
|
||||
return macos_tiny_not_eligible('module init symbol `${name}` is not supported')
|
||||
}
|
||||
range := w.text_range(name)!
|
||||
seen[name] = true
|
||||
selected << name
|
||||
for reloc in w.macho.relocs {
|
||||
if reloc.addr < int(range.start) || reloc.addr >= int(range.end) {
|
||||
continue
|
||||
}
|
||||
validate_macho_tiny_relocation(reloc)!
|
||||
sym := w.reloc_symbol(reloc)!
|
||||
if sym.sect == ObjectFormat.macho.section_index(.text) {
|
||||
if !seen[sym.name] {
|
||||
queue << sym.name
|
||||
}
|
||||
} else if sym.sect == ObjectFormat.macho.section_index(.rodata) {
|
||||
w.add_data_range(mut selected_data, .rodata, sym.value)!
|
||||
} else if sym.sect == ObjectFormat.macho.section_index(.data) {
|
||||
if x64_main_argc_global_name(sym.name) || x64_main_argv_global_name(sym.name) {
|
||||
return macos_tiny_not_eligible('arguments() requires hosted argc/argv; macOS tiny _main argv is not implemented yet')
|
||||
}
|
||||
w.add_data_range(mut selected_data, .data, sym.value)!
|
||||
} else if sym.sect == 0 && sym.type_ == 0x01 {
|
||||
undefined[sym.name] = true
|
||||
} else {
|
||||
return macos_tiny_not_eligible('relocation to symbol `${sym.name}` in Mach-O section ${sym.sect} is not supported')
|
||||
}
|
||||
}
|
||||
}
|
||||
return MachOTinyReachable{
|
||||
names: selected
|
||||
data: selected_data
|
||||
undefined: undefined
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_macho_tiny_relocation(reloc MachORelocationInfo) ! {
|
||||
if !reloc.pcrel || reloc.length != 2 {
|
||||
return macos_tiny_not_eligible('non-pcrel or non-32-bit relocation at __text+${reloc.addr} is not supported')
|
||||
}
|
||||
if reloc.type_ !in [x86_64_reloc_signed, x86_64_reloc_branch, x86_64_reloc_got_load,
|
||||
x86_64_reloc_got] {
|
||||
return macos_tiny_not_eligible('Mach-O relocation type ${reloc.type_} is not supported')
|
||||
}
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) text_range(name string) !MachOTextRange {
|
||||
ranges := w.text_symbol_ranges()
|
||||
for range in ranges {
|
||||
if range.name == name {
|
||||
return range
|
||||
}
|
||||
}
|
||||
return error('missing text symbol `${name}`')
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) text_symbol_ranges() []MachOTextRange {
|
||||
mut syms := []MachOSymbol{}
|
||||
for sym in w.macho.symbols {
|
||||
if sym.name != '' && sym.sect == ObjectFormat.macho.section_index(.text) {
|
||||
syms << sym
|
||||
}
|
||||
}
|
||||
for i := 1; i < syms.len; i++ {
|
||||
mut j := i
|
||||
for j > 0 && syms[j - 1].value > syms[j].value {
|
||||
syms[j - 1], syms[j] = syms[j], syms[j - 1]
|
||||
j--
|
||||
}
|
||||
}
|
||||
mut ranges := []MachOTextRange{}
|
||||
for i, sym in syms {
|
||||
mut end := u64(w.macho.text_data.len)
|
||||
for j := i + 1; j < syms.len; j++ {
|
||||
if syms[j].value > sym.value {
|
||||
end = syms[j].value
|
||||
break
|
||||
}
|
||||
}
|
||||
if end > sym.value {
|
||||
ranges << MachOTextRange{
|
||||
name: sym.name
|
||||
start: sym.value
|
||||
end: end
|
||||
}
|
||||
}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) reloc_symbol(reloc MachORelocationInfo) !MachOSymbol {
|
||||
if reloc.sym_idx < 0 || reloc.sym_idx >= w.macho.symbols.len {
|
||||
return error('macOS tiny object relocation references invalid symbol index ${reloc.sym_idx}')
|
||||
}
|
||||
return w.macho.symbols[reloc.sym_idx]
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) symbol_by_name(name string) !MachOSymbol {
|
||||
idx := w.macho.sym_by_name[name] or { return error('missing symbol `${name}`') }
|
||||
if idx < 0 || idx >= w.macho.symbols.len {
|
||||
return error('invalid symbol index ${idx} for `${name}`')
|
||||
}
|
||||
return w.macho.symbols[idx]
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) symbol_object_section(sym MachOSymbol) !ObjectSection {
|
||||
if sym.sect == ObjectFormat.macho.section_index(.rodata) {
|
||||
return .rodata
|
||||
}
|
||||
if sym.sect == ObjectFormat.macho.section_index(.data) {
|
||||
return .data
|
||||
}
|
||||
if sym.sect == ObjectFormat.macho.section_index(.text) {
|
||||
return .text
|
||||
}
|
||||
return error('symbol `${sym.name}` is not in a known Mach-O section')
|
||||
}
|
||||
|
||||
fn (mut w MachOTinyObjectWriter) add_data_range(mut ranges []MachODataRange, section ObjectSection, start u64) ! {
|
||||
end := w.data_symbol_range_end(section, start)!
|
||||
for range in ranges {
|
||||
if range.section == section && range.start == start && range.end == end {
|
||||
return
|
||||
}
|
||||
}
|
||||
ranges << MachODataRange{
|
||||
section: section
|
||||
start: start
|
||||
end: end
|
||||
}
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) data_symbol_range_end(section ObjectSection, start u64) !u64 {
|
||||
section_idx := ObjectFormat.macho.section_index(section)
|
||||
section_len := match section {
|
||||
.rodata { u64(w.macho.rodata.len) }
|
||||
.data { u64(w.macho.data_data.len) }
|
||||
else { u64(0) }
|
||||
}
|
||||
|
||||
mut end := section_len
|
||||
for sym in w.macho.symbols {
|
||||
if sym.sect == section_idx && sym.value > start && sym.value < end {
|
||||
end = sym.value
|
||||
}
|
||||
}
|
||||
if end < start {
|
||||
return error('invalid Mach-O data symbol range')
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) copy_data_ranges(ranges []MachODataRange, section ObjectSection, mut out []u8, mut offsets map[string]u64) ! {
|
||||
for dr in ranges {
|
||||
if dr.section != section {
|
||||
continue
|
||||
}
|
||||
align := int(w.data_section_alignment(section))
|
||||
for align > 1 && out.len % align != int(dr.start % u64(align)) {
|
||||
out << u8(0)
|
||||
}
|
||||
off := u64(out.len)
|
||||
section_idx := ObjectFormat.macho.section_index(section)
|
||||
for sym in w.macho.symbols {
|
||||
if sym.sect == section_idx && sym.value == dr.start {
|
||||
offsets[sym.name] = off
|
||||
}
|
||||
}
|
||||
match section {
|
||||
.rodata {
|
||||
out << w.macho.rodata[int(dr.start)..int(dr.end)]
|
||||
}
|
||||
.data {
|
||||
out << w.macho.data_data[int(dr.start)..int(dr.end)]
|
||||
}
|
||||
else {
|
||||
return error('macOS tiny object cannot copy ${section} as data')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) data_section_alignment(section ObjectSection) u64 {
|
||||
return match section {
|
||||
.data { u64(8) }
|
||||
.rodata { u64(4) }
|
||||
else { u64(1) }
|
||||
}
|
||||
}
|
||||
|
||||
fn macho_tiny_defined_symbol_is_external(name string) bool {
|
||||
return name == '_main'
|
||||
}
|
||||
|
||||
fn macho_tiny_resolve_relocation_locally(name string, reloc MachORelocationInfo) bool {
|
||||
return name in [macos_tiny_builtin_string_plus_symbol, macos_tiny_builtin_i64_str_symbol]
|
||||
&& reloc.type_ == x86_64_reloc_branch && reloc.pcrel && reloc.length == 2
|
||||
}
|
||||
|
||||
fn macho_tiny_add_runtime_helpers(mut out MachOObject, mut reachable MachOTinyReachable, mut func_offsets map[string]u64, mut runtime_symbols []string, mut runtime_relocs []MachOTinyRuntimeReloc) {
|
||||
if reachable.undefined[macos_tiny_builtin_i64_str_symbol] {
|
||||
reachable.undefined.delete(macos_tiny_builtin_i64_str_symbol)
|
||||
reachable.undefined[macos_tiny_malloc_symbol] = true
|
||||
reachable.undefined[macos_tiny_exit_symbol] = true
|
||||
func_offsets[macos_tiny_builtin_i64_str_symbol] = u64(out.text_data.len)
|
||||
runtime_symbols << macos_tiny_builtin_i64_str_symbol
|
||||
macho_tiny_emit_i64_str(mut out.text_data, mut runtime_relocs)
|
||||
}
|
||||
if reachable.undefined[macos_tiny_builtin_string_plus_symbol] {
|
||||
reachable.undefined.delete(macos_tiny_builtin_string_plus_symbol)
|
||||
reachable.undefined[macos_tiny_malloc_symbol] = true
|
||||
reachable.undefined[macos_tiny_exit_symbol] = true
|
||||
func_offsets[macos_tiny_builtin_string_plus_symbol] = u64(out.text_data.len)
|
||||
runtime_symbols << macos_tiny_builtin_string_plus_symbol
|
||||
macho_tiny_emit_string_plus(mut out.text_data, mut runtime_relocs)
|
||||
}
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_i64_str(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc) {
|
||||
text << [
|
||||
u8(0x57), // push rdi, input value
|
||||
0xbf,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov edi, 32
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_malloc_symbol)
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
malloc_ok := macho_tiny_emit_jcc32(mut text, 0x85) // jne ok
|
||||
text << [
|
||||
u8(0xbf),
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov edi, 1
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_exit_symbol)
|
||||
text << [u8(0x0f), 0x0b] // ud2
|
||||
ok_start := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, malloc_ok, ok_start)
|
||||
|
||||
text << [
|
||||
u8(0x5f), // pop rdi
|
||||
0x4c,
|
||||
0x8d,
|
||||
0x40,
|
||||
0x1f, // lea r8, [rax+31]
|
||||
0x41,
|
||||
0xc6,
|
||||
0x00,
|
||||
0x00, // mov byte ptr [r8], 0
|
||||
0x48,
|
||||
0x89,
|
||||
0xf8, // mov rax, rdi
|
||||
0x45,
|
||||
0x31,
|
||||
0xc9, // xor r9d, r9d
|
||||
0x45,
|
||||
0x31,
|
||||
0xd2, // xor r10d, r10d
|
||||
0x48,
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
non_negative := macho_tiny_emit_jcc32(mut text, 0x89) // jns non_negative
|
||||
text << [
|
||||
u8(0x41),
|
||||
0xb2,
|
||||
0x01, // mov r10b, 1
|
||||
0x48,
|
||||
0xf7,
|
||||
0xd8, // neg rax
|
||||
]
|
||||
non_negative_target := text.len
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
non_zero := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
text << [
|
||||
u8(0x49),
|
||||
0xff,
|
||||
0xc8, // dec r8
|
||||
0x41,
|
||||
0xc6,
|
||||
0x00,
|
||||
0x30, // mov byte ptr [r8], '0'
|
||||
0x41,
|
||||
0xb9,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov r9d, 1
|
||||
]
|
||||
maybe_sign_jump := macho_tiny_emit_jmp32(mut text)
|
||||
loop_start := text.len
|
||||
text << [
|
||||
u8(0x31),
|
||||
0xd2, // xor edx, edx
|
||||
0xb9,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov ecx, 10
|
||||
0x48,
|
||||
0xf7,
|
||||
0xf1, // div rcx
|
||||
0x80,
|
||||
0xc2,
|
||||
0x30, // add dl, '0'
|
||||
0x49,
|
||||
0xff,
|
||||
0xc8, // dec r8
|
||||
0x41,
|
||||
0x88,
|
||||
0x10, // mov [r8], dl
|
||||
0x49,
|
||||
0xff,
|
||||
0xc1, // inc r9
|
||||
0x48,
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
loop_more := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
maybe_sign := text.len
|
||||
text << [
|
||||
u8(0x45),
|
||||
0x84,
|
||||
0xd2, // test r10b, r10b
|
||||
]
|
||||
done_digits := macho_tiny_emit_jcc32(mut text, 0x84) // je done
|
||||
text << [
|
||||
u8(0x49),
|
||||
0xff,
|
||||
0xc8, // dec r8
|
||||
0x41,
|
||||
0xc6,
|
||||
0x00,
|
||||
0x2d, // mov byte ptr [r8], '-'
|
||||
0x49,
|
||||
0xff,
|
||||
0xc1, // inc r9
|
||||
]
|
||||
done_digits_target := text.len
|
||||
text << [
|
||||
u8(0x4c),
|
||||
0x89,
|
||||
0xc0, // mov rax, r8
|
||||
0x4c,
|
||||
0x89,
|
||||
0xca, // mov rdx, r9
|
||||
0xc3, // ret
|
||||
]
|
||||
macho_tiny_patch_rel32_local(mut text, non_negative, non_negative_target)
|
||||
macho_tiny_patch_rel32_local(mut text, non_zero, loop_start)
|
||||
macho_tiny_patch_rel32_local(mut text, maybe_sign_jump, maybe_sign)
|
||||
macho_tiny_patch_rel32_local(mut text, loop_more, loop_start)
|
||||
macho_tiny_patch_rel32_local(mut text, done_digits, done_digits_target)
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_string_plus(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc) {
|
||||
text << [
|
||||
u8(0x57), // push rdi, first string ptr
|
||||
0x56, // push rsi, first string len
|
||||
0x52, // push rdx, second string ptr
|
||||
0x51, // push rcx, second string len
|
||||
0x48,
|
||||
0x83,
|
||||
0xec,
|
||||
0x08, // sub rsp, 8, align before libSystem calls
|
||||
0x44,
|
||||
0x8b,
|
||||
0x44,
|
||||
0x24,
|
||||
0x18, // mov r8d, [rsp+24]
|
||||
0x44,
|
||||
0x03,
|
||||
0x44,
|
||||
0x24,
|
||||
0x08, // add r8d, [rsp+8]
|
||||
]
|
||||
len_overflow := macho_tiny_emit_jcc32(mut text, 0x82) // jc fail
|
||||
text << [
|
||||
u8(0x4d),
|
||||
0x89,
|
||||
0xc1, // mov r9, r8
|
||||
0x49,
|
||||
0x83,
|
||||
0xc1,
|
||||
0x01, // add r9, 1
|
||||
]
|
||||
alloc_overflow := macho_tiny_emit_jcc32(mut text, 0x82) // jc fail
|
||||
text << [
|
||||
u8(0x4c),
|
||||
0x89,
|
||||
0xcf, // mov rdi, r9
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_malloc_symbol)
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
malloc_ok := macho_tiny_emit_jcc32(mut text, 0x85) // jne ok
|
||||
fail_start := text.len
|
||||
text << [
|
||||
u8(0xbf),
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov edi, 1
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_exit_symbol)
|
||||
text << [u8(0x0f), 0x0b] // ud2
|
||||
ok_start := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, len_overflow, fail_start)
|
||||
macho_tiny_patch_rel32_local(mut text, alloc_overflow, fail_start)
|
||||
macho_tiny_patch_rel32_local(mut text, malloc_ok, ok_start)
|
||||
|
||||
text << [
|
||||
u8(0x49),
|
||||
0x89,
|
||||
0xc2, // mov r10, rax
|
||||
0x49,
|
||||
0x89,
|
||||
0xc0, // mov r8, rax
|
||||
0x48,
|
||||
0x8b,
|
||||
0x74,
|
||||
0x24,
|
||||
0x20, // mov rsi, [rsp+32]
|
||||
0x8b,
|
||||
0x4c,
|
||||
0x24,
|
||||
0x18, // mov ecx, [rsp+24]
|
||||
0x48,
|
||||
0x85,
|
||||
0xc9, // test rcx, rcx
|
||||
]
|
||||
copy_a_done := macho_tiny_emit_jcc32(mut text, 0x84) // jz done
|
||||
copy_a_loop := text.len
|
||||
text << [
|
||||
u8(0x8a),
|
||||
0x16, // mov dl, [rsi]
|
||||
0x41,
|
||||
0x88,
|
||||
0x10, // mov [r8], dl
|
||||
0x48,
|
||||
0xff,
|
||||
0xc6, // inc rsi
|
||||
0x49,
|
||||
0xff,
|
||||
0xc0, // inc r8
|
||||
0x48,
|
||||
0xff,
|
||||
0xc9, // dec rcx
|
||||
]
|
||||
copy_a_continue := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
copy_a_done_target := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, copy_a_done, copy_a_done_target)
|
||||
macho_tiny_patch_rel32_local(mut text, copy_a_continue, copy_a_loop)
|
||||
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x8b,
|
||||
0x74,
|
||||
0x24,
|
||||
0x10, // mov rsi, [rsp+16]
|
||||
0x8b,
|
||||
0x4c,
|
||||
0x24,
|
||||
0x08, // mov ecx, [rsp+8]
|
||||
0x48,
|
||||
0x85,
|
||||
0xc9, // test rcx, rcx
|
||||
]
|
||||
copy_b_done := macho_tiny_emit_jcc32(mut text, 0x84) // jz done
|
||||
copy_b_loop := text.len
|
||||
text << [
|
||||
u8(0x8a),
|
||||
0x16, // mov dl, [rsi]
|
||||
0x41,
|
||||
0x88,
|
||||
0x10, // mov [r8], dl
|
||||
0x48,
|
||||
0xff,
|
||||
0xc6, // inc rsi
|
||||
0x49,
|
||||
0xff,
|
||||
0xc0, // inc r8
|
||||
0x48,
|
||||
0xff,
|
||||
0xc9, // dec rcx
|
||||
]
|
||||
copy_b_continue := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
copy_b_done_target := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, copy_b_done, copy_b_done_target)
|
||||
macho_tiny_patch_rel32_local(mut text, copy_b_continue, copy_b_loop)
|
||||
|
||||
text << [
|
||||
u8(0x41),
|
||||
0xc6,
|
||||
0x00,
|
||||
0x00, // mov byte ptr [r8], 0
|
||||
0x8b,
|
||||
0x44,
|
||||
0x24,
|
||||
0x18, // mov eax, [rsp+24]
|
||||
0x03,
|
||||
0x44,
|
||||
0x24,
|
||||
0x08, // add eax, [rsp+8]
|
||||
0x48,
|
||||
0x89,
|
||||
0xc2, // mov rdx, rax
|
||||
0x4c,
|
||||
0x89,
|
||||
0xd0, // mov rax, r10
|
||||
0x48,
|
||||
0x83,
|
||||
0xc4,
|
||||
0x28, // add rsp, 40
|
||||
0xc3, // ret
|
||||
]
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_call_reloc(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc, symbol string) {
|
||||
text << u8(0xe8)
|
||||
field_off := text.len
|
||||
text << [u8(0), 0, 0, 0]
|
||||
runtime_relocs << MachOTinyRuntimeReloc{
|
||||
addr: field_off
|
||||
symbol: symbol
|
||||
}
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_jcc32(mut text []u8, opcode u8) int {
|
||||
text << [u8(0x0f), opcode]
|
||||
field_off := text.len
|
||||
text << [u8(0), 0, 0, 0]
|
||||
return field_off
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_jmp32(mut text []u8) int {
|
||||
text << u8(0xe9)
|
||||
field_off := text.len
|
||||
text << [u8(0), 0, 0, 0]
|
||||
return field_off
|
||||
}
|
||||
|
||||
fn macho_tiny_patch_rel32_local(mut text []u8, field_off int, target_off int) {
|
||||
disp := i64(target_off) - i64(field_off + 4)
|
||||
binary.little_endian_put_u32(mut text[field_off..field_off + 4], u32(i32(disp)))
|
||||
}
|
||||
|
||||
fn macho_tiny_is_module_init_symbol(name string) bool {
|
||||
return name.ends_with('__init') && name != '_main'
|
||||
}
|
||||
|
||||
fn macos_tiny_not_eligible(message string) IError {
|
||||
return error(macos_tiny_not_eligible_prefix + message)
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module x64
|
||||
|
||||
import encoding.binary
|
||||
|
||||
pub enum ObjectFormat {
|
||||
elf
|
||||
macho
|
||||
coff
|
||||
}
|
||||
|
||||
enum ObjectSection {
|
||||
text
|
||||
data
|
||||
rodata
|
||||
}
|
||||
|
||||
fn (format ObjectFormat) section_index(section ObjectSection) u8 {
|
||||
return match format {
|
||||
.elf {
|
||||
match section {
|
||||
.text { 1 }
|
||||
.data { 2 }
|
||||
.rodata { 3 }
|
||||
}
|
||||
}
|
||||
.macho {
|
||||
match section {
|
||||
.text { 1 }
|
||||
.rodata { 2 }
|
||||
.data { 3 }
|
||||
}
|
||||
}
|
||||
.coff {
|
||||
match section {
|
||||
.text { 1 }
|
||||
.rodata { 2 }
|
||||
.data { 3 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (format ObjectFormat) symbol_name(name string) string {
|
||||
if format == .macho && !name.starts_with('L_') {
|
||||
return '_' + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn (mut g Gen) text_len() int {
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.text_data.len }
|
||||
.macho { g.macho.text_data.len }
|
||||
.coff { g.coff.text_data.len }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) data_len() int {
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.data_data.len }
|
||||
.macho { g.macho.data_data.len }
|
||||
.coff { g.coff.data_data.len }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) rodata_len() int {
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.rodata.len }
|
||||
.macho { g.macho.rodata.len }
|
||||
.coff { g.coff.rodata.len }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_data_byte(b u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.data_data << b }
|
||||
.macho { g.macho.data_data << b }
|
||||
.coff { g.coff.data_data << b }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_data(bytes []u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.data_data << bytes }
|
||||
.macho { g.macho.data_data << bytes }
|
||||
.coff { g.coff.data_data << bytes }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_rodata_byte(b u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.rodata << b }
|
||||
.macho { g.macho.rodata << b }
|
||||
.coff { g.coff.rodata << b }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_rodata(bytes []u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.rodata << bytes }
|
||||
.macho { g.macho.rodata << bytes }
|
||||
.coff { g.coff.rodata << bytes }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_symbol(name string, value u64, is_func bool, section ObjectSection) int {
|
||||
sym_name := g.obj_format.symbol_name(name)
|
||||
sect := g.obj_format.section_index(section)
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.add_symbol(sym_name, value, is_func, u16(sect)) }
|
||||
.macho { g.macho.add_symbol(sym_name, value, !sym_name.starts_with('L_'), sect) }
|
||||
.coff { g.coff.add_symbol(sym_name, value, is_func, sect) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_undefined(name string) int {
|
||||
sym_name := g.obj_format.symbol_name(name)
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.add_undefined(sym_name) }
|
||||
.macho { g.macho.add_undefined(sym_name) }
|
||||
.coff { g.coff.add_undefined(sym_name) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_call_reloc(sym_idx int) {
|
||||
offset := g.text_len()
|
||||
match g.obj_format {
|
||||
.elf { g.elf.add_text_reloc(u64(offset), sym_idx, r_x86_64_plt32, -4) }
|
||||
.macho { g.macho.add_reloc(offset, sym_idx, x86_64_reloc_branch, true, 2) }
|
||||
.coff { g.coff.add_text_reloc(offset, sym_idx, coff_image_rel_amd64_rel32) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_rip_reloc(sym_idx int) {
|
||||
offset := g.text_len()
|
||||
match g.obj_format {
|
||||
.elf { g.elf.add_text_reloc(u64(offset), sym_idx, r_x86_64_pc32, -4) }
|
||||
.macho { g.macho.add_reloc(offset, sym_idx, x86_64_reloc_signed, true, 2) }
|
||||
.coff { g.coff.add_text_reloc(offset, sym_idx, coff_image_rel_amd64_rel32) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_macho_got_load_reloc(sym_idx int) {
|
||||
offset := g.text_len()
|
||||
if g.obj_format == .macho {
|
||||
g.macho.add_reloc(offset, sym_idx, x86_64_reloc_got_load, true, 2)
|
||||
return
|
||||
}
|
||||
x64_unsupported('Mach-O GOT_LOAD relocation requested for ${g.obj_format}')
|
||||
}
|
||||
|
||||
fn (mut g Gen) emit(b u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.text_data << b }
|
||||
.macho { g.macho.text_data << b }
|
||||
.coff { g.coff.text_data << b }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) emit_u32(v u32) {
|
||||
g.emit(u8(v))
|
||||
g.emit(u8(v >> 8))
|
||||
g.emit(u8(v >> 16))
|
||||
g.emit(u8(v >> 24))
|
||||
}
|
||||
|
||||
fn (mut g Gen) emit_u64(v u64) {
|
||||
g.emit_u32(u32(v))
|
||||
g.emit_u32(u32(v >> 32))
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_u32(off int, v u32) {
|
||||
match g.obj_format {
|
||||
.elf { binary.little_endian_put_u32(mut g.elf.text_data[off..off + 4], v) }
|
||||
.macho { binary.little_endian_put_u32(mut g.macho.text_data[off..off + 4], v) }
|
||||
.coff { binary.little_endian_put_u32(mut g.coff.text_data[off..off + 4], v) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut g Gen) write_file(path string) {
|
||||
if msg := g.unsupported_external_symbol_message() {
|
||||
x64_abort_with_diagnostic(msg)
|
||||
}
|
||||
match g.obj_format {
|
||||
.elf { g.elf.write(path) }
|
||||
.macho { g.macho.write(path) }
|
||||
.coff { g.coff.write(path) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (g Gen) unsupported_external_symbol_message() ?string {
|
||||
for raw_name in g.undefined_external_symbol_names() {
|
||||
if msg := unsupported_external_symbol_message_for_name(g.obj_format, raw_name,
|
||||
'needed while preparing native x64 output')
|
||||
{
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (g Gen) undefined_external_symbol_names() []string {
|
||||
mut names := []string{}
|
||||
match g.obj_format {
|
||||
.elf {
|
||||
for sym in g.elf.symbols {
|
||||
if sym.name != '' && sym.shndx == 0 {
|
||||
names << sym.name
|
||||
}
|
||||
}
|
||||
}
|
||||
.macho {
|
||||
for sym in g.macho.symbols {
|
||||
if sym.name != '' && sym.sect == 0 && sym.type_ == 0x01 {
|
||||
names << sym.name
|
||||
}
|
||||
}
|
||||
}
|
||||
.coff {
|
||||
for sym in g.coff.symbols {
|
||||
if sym.name != '' && sym.section == 0
|
||||
&& sym.storage_class == coff_image_sym_class_external {
|
||||
names << sym.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
pub fn (mut g Gen) link_executable(path string) ! {
|
||||
match g.obj_format {
|
||||
.coff {
|
||||
if g.abi != .windows {
|
||||
return error('x64 PE linking requires Windows ABI code generation')
|
||||
}
|
||||
mut linker := PeLinker.new(g.coff)
|
||||
linker.write(path)!
|
||||
}
|
||||
else {
|
||||
return error('x64 built-in executable linking is only implemented for COFF')
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,276 @@
|
||||
module x64
|
||||
|
||||
import os
|
||||
import v2.mir
|
||||
|
||||
struct X64BackendCompileFailure {
|
||||
stdout string
|
||||
stderr string
|
||||
exit_code int
|
||||
}
|
||||
|
||||
fn run_x64_backend_compile_failure(name string, source string) X64BackendCompileFailure {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_diagnostics_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := x64_backend_diagnostic_vexe_command_path()
|
||||
mut build := os.new_process(vexe)
|
||||
defer {
|
||||
build.close()
|
||||
}
|
||||
build.set_args(['-v2', '-b', 'x64', source_path, '-o', bin_path])
|
||||
build.set_redirect_stdio()
|
||||
build.run()
|
||||
build.wait()
|
||||
stdout := x64_backend_diagnostic_stdout(build.stdout_slurp())
|
||||
stderr := build.stderr_slurp()
|
||||
return X64BackendCompileFailure{
|
||||
stdout: stdout
|
||||
stderr: stderr
|
||||
exit_code: build.code
|
||||
}
|
||||
}
|
||||
|
||||
fn x64_backend_diagnostic_vexe_command_path() string {
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
if os.is_abs_path(vexe) || !os.exists(vexe) {
|
||||
return vexe
|
||||
}
|
||||
return os.abs_path(vexe)
|
||||
}
|
||||
|
||||
fn x64_backend_diagnostic_stdout(stdout string) string {
|
||||
mut lines := []string{}
|
||||
for line in stdout.split_into_lines() {
|
||||
if line.starts_with(' * ') {
|
||||
continue
|
||||
}
|
||||
lines << line
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
fn assert_x64_user_visible_compile_failure(failure X64BackendCompileFailure, expected_message string) {
|
||||
assert failure.exit_code != 0, failure.stdout + failure.stderr
|
||||
assert failure.stdout == '', failure.stdout
|
||||
assert failure.stderr.contains(expected_message), failure.stderr
|
||||
assert failure.stderr.contains(x64_backend_limitation_hint), failure.stderr
|
||||
assert !failure.stderr.contains('Link failed:'), failure.stderr
|
||||
assert !failure.stderr.contains('V panic:'), failure.stderr
|
||||
assert !failure.stderr.contains('Backtrace'), failure.stderr
|
||||
assert_x64_message_avoids_old_wording(failure.stderr)
|
||||
}
|
||||
|
||||
fn assert_x64_clean_user_diagnostic_message(msg string) {
|
||||
assert msg.starts_with('x64: unsupported backend feature: '), msg
|
||||
assert !msg.contains('Link failed:'), msg
|
||||
assert !msg.contains('V panic:'), msg
|
||||
assert !msg.contains('Backtrace'), msg
|
||||
assert_x64_message_avoids_old_wording(msg)
|
||||
}
|
||||
|
||||
fn assert_x64_message_avoids_old_wording(msg string) {
|
||||
assert !msg.contains('reachable helper'), msg
|
||||
assert !msg.contains('stdout/stderr path'), msg
|
||||
assert !msg.contains('must be compiled'), msg
|
||||
assert !msg.contains('lowered before linking'), msg
|
||||
assert !msg.contains('before external linking'), msg
|
||||
assert !msg.contains('cannot resolve C stdio symbol'), msg
|
||||
assert !msg.contains('C FILE streams'), msg
|
||||
}
|
||||
|
||||
fn assert_x64_message_mentions_only_target_linker(format ObjectFormat, msg string) {
|
||||
match format {
|
||||
.elf {
|
||||
assert msg.contains('ELF linker'), msg
|
||||
assert !msg.contains('Mach-O'), msg
|
||||
assert !msg.contains('PE linker'), msg
|
||||
assert !msg.contains('Windows'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('macOS'), msg
|
||||
}
|
||||
.macho {
|
||||
assert msg.contains('Mach-O linker'), msg
|
||||
assert !msg.contains('ELF linker'), msg
|
||||
assert !msg.contains('PE linker'), msg
|
||||
assert !msg.contains('Windows'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('Linux'), msg
|
||||
}
|
||||
.coff {
|
||||
assert msg.contains('PE linker'), msg
|
||||
assert !msg.contains('ELF linker'), msg
|
||||
assert !msg.contains('Mach-O'), msg
|
||||
assert !msg.contains('Linux'), msg
|
||||
assert !msg.contains('macOS'), msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_stderr_reports_clean_codegen_abi_unsupported_without_link_noise() {
|
||||
$if windows {
|
||||
println('skipping ${@FN}: SysV codegen diagnostic is not available on Windows')
|
||||
} $else $if x64 {
|
||||
failure := run_x64_backend_compile_failure('clean_codegen_abi_unsupported', 'module main
|
||||
|
||||
fn many(a0 f64, a1 f64, a2 f64, a3 f64, a4 f64, a5 f64, a6 f64, a7 f64, a8 f64) f64 {
|
||||
return a8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = many(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)
|
||||
}
|
||||
')
|
||||
assert_x64_user_visible_compile_failure(failure,
|
||||
'x64: unsupported backend feature: stack-passed float parameter')
|
||||
assert !failure.stderr.contains('Windows'), failure.stderr
|
||||
assert !failure.stderr.contains('Kernel32'), failure.stderr
|
||||
assert !failure.stderr.contains('Mach-O'), failure.stderr
|
||||
assert !failure.stderr.contains('PE linker'), failure.stderr
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_stderr_reports_captured_fn_literal_unsupported() {
|
||||
$if x64 {
|
||||
failure := run_x64_backend_compile_failure('captured_fn_literal_unsupported', 'module main
|
||||
|
||||
fn make_delta(delta int) fn (int) int {
|
||||
return fn [delta] (value int) int {
|
||||
return value + delta
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
f := make_delta(10)
|
||||
println(f(1))
|
||||
}
|
||||
')
|
||||
assert_x64_user_visible_compile_failure(failure, 'x64: unsupported backend feature: ')
|
||||
assert failure.stderr.contains('native x64 cannot lower captured function literal'), failure.stderr
|
||||
|
||||
assert failure.stderr.contains('closure environments are not implemented yet'), failure.stderr
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_unsupported_backend_feature_message_is_normalized() {
|
||||
assert x64_unsupported_backend_feature_message('stack-passed float parameter') == 'x64: unsupported backend feature: stack-passed float parameter'
|
||||
assert x64_unsupported_backend_feature_message('backend feature: Windows argument lowering') == 'x64: unsupported backend feature: Windows argument lowering'
|
||||
assert x64_unsupported_backend_feature_message('backend feature: SysV direct aggregate call result with MEMORY eightbyte classes is not implemented yet') == 'x64: unsupported backend feature: SysV direct aggregate call result with MEMORY eightbyte classes is not implemented yet'
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_linker_diagnostic_message_for_generic_external_by_format() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
msg := x64_unresolved_external_symbol_message(format, 'v_missing_runtime_symbol',
|
||||
'referenced from test relocation')
|
||||
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains('cannot resolve external symbol `v_missing_runtime_symbol` yet'), msg
|
||||
assert msg.contains('referenced from test relocation'), msg
|
||||
assert !msg.contains('C stdio/file-descriptor symbol'), msg
|
||||
assert !msg.contains('Windows'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('C FILE/stdio'), msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_crt_stdio_and_fd_diagnostic_is_windows_only() {
|
||||
for symbol_name in ['stderr', 'fread', '_get_osfhandle', '_open_osfhandle'] {
|
||||
for format in [ObjectFormat.elf, .macho] {
|
||||
msg := x64_unresolved_external_symbol_message(format, symbol_name,
|
||||
'referenced from test relocation')
|
||||
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains('cannot resolve external symbol `${symbol_name}` yet'), msg
|
||||
assert !msg.contains('C stdio/file-descriptor symbol'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('C FILE/stdio'), msg
|
||||
}
|
||||
|
||||
pe_msg := x64_unresolved_external_symbol_message(.coff, symbol_name,
|
||||
'referenced from test relocation')
|
||||
assert_x64_clean_user_diagnostic_message(pe_msg)
|
||||
assert_x64_message_mentions_only_target_linker(.coff, pe_msg)
|
||||
assert pe_msg.contains('cannot resolve C stdio/file-descriptor symbol `${symbol_name}`'), pe_msg
|
||||
assert pe_msg.contains('Windows x64 native backend uses Kernel32 handles'), pe_msg
|
||||
assert pe_msg.contains('Kernel32 handles'), pe_msg
|
||||
assert pe_msg.contains('C FILE/stdio calls'), pe_msg
|
||||
assert !pe_msg.contains('Linux'), pe_msg
|
||||
assert !pe_msg.contains('macOS'), pe_msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_linker_diagnostic_message_for_missing_runtime_helper_by_format() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
msg := x64_unresolved_external_symbol_message(format, 'builtin__Map_string_int__keys',
|
||||
'referenced from test relocation')
|
||||
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`'), msg
|
||||
assert msg.contains('native x64 backend does not implement this feature for this target yet'), msg
|
||||
assert msg.contains('referenced from test relocation'), msg
|
||||
assert !msg.contains('C stdio/file-descriptor symbol'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('C FILE/stdio'), msg
|
||||
assert !msg.contains('not imported'), msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_gen_detects_missing_runtime_helper_while_preparing_output() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
mut mod := mir.Module{}
|
||||
mut gen := Gen.new_with_format(&mod, format)
|
||||
match format {
|
||||
.elf {
|
||||
gen.elf.add_undefined('builtin__Map_string_int__keys')
|
||||
}
|
||||
.macho {
|
||||
gen.macho.add_undefined('_builtin__Map_string_int__keys')
|
||||
}
|
||||
.coff {
|
||||
gen.coff.add_undefined('builtin__Map_string_int__keys')
|
||||
}
|
||||
}
|
||||
|
||||
msg := gen.unsupported_external_symbol_message() or {
|
||||
assert false
|
||||
continue
|
||||
}
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains(x64_linker_name(format)), msg
|
||||
assert msg.contains('builtin__Map_string_int__keys'), msg
|
||||
assert msg.contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`'), msg
|
||||
assert msg.contains('native x64 backend does not implement this feature for this target yet'), msg
|
||||
assert msg.contains('needed while preparing native x64 output'), msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_gen_keeps_linker_resolved_external_symbols_for_external_linkers() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
mut mod := mir.Module{}
|
||||
mut gen := Gen.new_with_format(&mod, format)
|
||||
match format {
|
||||
.elf {
|
||||
gen.elf.add_undefined('calloc')
|
||||
}
|
||||
.macho {
|
||||
gen.macho.add_undefined('_calloc')
|
||||
}
|
||||
.coff {
|
||||
gen.coff.add_undefined('calloc')
|
||||
}
|
||||
}
|
||||
|
||||
if msg := gen.unsupported_external_symbol_message() {
|
||||
assert false, msg
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
// vtest build: false
|
||||
|
||||
module x64
|
||||
|
||||
import os
|
||||
|
||||
fn run_x64_backend_runtime_program(name string, source string) string {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_runtime_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
build :=
|
||||
os.execute('${os.quoted_path(vexe)} -v2 -b x64 ${os.quoted_path(source_path)} -o ${os.quoted_path(bin_path)}')
|
||||
assert build.exit_code == 0, build.output
|
||||
run :=
|
||||
os.execute('out=$(${os.quoted_path(bin_path)} 2>&1); code=$?; printf "%s\\n%s" "$code" "$out"')
|
||||
assert run.exit_code == 0, '${name}: ${run.output}'
|
||||
lines := run.output.split_into_lines()
|
||||
assert lines.len >= 1, '${name}: ${run.output}'
|
||||
assert lines[0] == '0', '${name}: ${run.output}'
|
||||
return lines[1..].join('\n')
|
||||
}
|
||||
|
||||
fn run_x64_backend_runtime_compile_error(name string, source string) string {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_runtime_fail_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
build :=
|
||||
os.execute('${os.quoted_path(vexe)} -v2 -b x64 ${os.quoted_path(source_path)} -o ${os.quoted_path(bin_path)}')
|
||||
assert build.exit_code != 0, build.output
|
||||
return build.output
|
||||
}
|
||||
|
||||
fn run_x64_backend_runtime_program_redirected(name string, source string) (string, string) {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_runtime_redir_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
stdout_path := os.join_path(tmp_dir, '${name}.out')
|
||||
stderr_path := os.join_path(tmp_dir, '${name}.err')
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
build :=
|
||||
os.execute('${os.quoted_path(vexe)} -v2 -b x64 ${os.quoted_path(source_path)} -o ${os.quoted_path(bin_path)}')
|
||||
assert build.exit_code == 0, build.output
|
||||
run :=
|
||||
os.execute('${os.quoted_path(bin_path)} > ${os.quoted_path(stdout_path)} 2> ${os.quoted_path(stderr_path)}')
|
||||
assert run.exit_code == 0, run.output
|
||||
stdout := os.read_file(stdout_path) or { panic(err) }
|
||||
stderr := os.read_file(stderr_path) or { panic(err) }
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_hello_world_runs() {
|
||||
output := run_x64_backend_runtime_program('hello_world', "module main
|
||||
|
||||
fn main() {
|
||||
println('Hello World!')
|
||||
}
|
||||
")
|
||||
assert output == 'Hello World!'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_hello_world_runs_with_stdout_redirected() {
|
||||
stdout, stderr := run_x64_backend_runtime_program_redirected('hello_world_redir', "module main
|
||||
|
||||
fn main() {
|
||||
println('Hello World!')
|
||||
}
|
||||
")
|
||||
assert stdout == 'Hello World!\n'
|
||||
assert stderr == ''
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_const_init_is_not_skipped() {
|
||||
output := run_x64_backend_runtime_program('const_string', "module main
|
||||
|
||||
const greeting = 'Const hello'
|
||||
|
||||
fn main() {
|
||||
println(greeting)
|
||||
}
|
||||
")
|
||||
assert output == 'Const hello'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_branch_and_local_mutation_pipeline_correctness() {
|
||||
output := run_x64_backend_runtime_program('branch_local_mutation', "module main
|
||||
|
||||
fn adjustment(flag bool) int {
|
||||
if flag {
|
||||
return 7
|
||||
}
|
||||
return -3
|
||||
}
|
||||
|
||||
fn score(a int, b int, flag bool) int {
|
||||
mut total := a * 10
|
||||
total += adjustment(flag)
|
||||
if total > b {
|
||||
return total - b
|
||||
}
|
||||
return b - total
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if score(4, 35, true) == 12 && score(4, 35, false) == 2 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_trunc_and_zext_mask_integer_values() {
|
||||
output := run_x64_backend_runtime_program('int_cast_masks', "module main
|
||||
|
||||
fn narrow_and_widen(x u16) u64 {
|
||||
y := u8(x)
|
||||
return u64(y)
|
||||
}
|
||||
|
||||
fn narrow_u32_to_u16(x u32) u64 {
|
||||
y := u16(x)
|
||||
return u64(y)
|
||||
}
|
||||
|
||||
fn narrow_u64_to_u8(x u64) u64 {
|
||||
y := u8(x)
|
||||
return u64(y)
|
||||
}
|
||||
|
||||
fn sign_extend_i8(x i8) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn sign_extend_i16(x i16) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if narrow_and_widen(u16(0x1234)) == 0x34
|
||||
&& narrow_u32_to_u16(u32(0x12345678)) == 0x5678
|
||||
&& narrow_u64_to_u8(u64(0x123456789ABCDEFF)) == 0xFF
|
||||
&& sign_extend_i8(i8(-42)) == -42
|
||||
&& sign_extend_i16(i16(-12345)) == -12345 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_signed_memory_loads_extend_values() {
|
||||
output := run_x64_backend_runtime_program('signed_memory_loads', "module main
|
||||
|
||||
fn load_i8_from_ptr(p &i8) i64 {
|
||||
return i64(*p)
|
||||
}
|
||||
|
||||
fn load_i16_from_ptr(p &i16) i64 {
|
||||
return i64(*p)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut a := i8(-42)
|
||||
mut b := i16(-12345)
|
||||
if load_i8_from_ptr(&a) == -42 && load_i16_from_ptr(&b) == -12345 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_raw_copy_tails_do_not_overwrite_sentinel() {
|
||||
output := run_x64_backend_runtime_program('raw_copy_tails', "module main
|
||||
|
||||
struct Tiny3 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
}
|
||||
|
||||
struct Wrap3 {
|
||||
mut:
|
||||
data Tiny3
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
struct Tiny5 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
}
|
||||
|
||||
struct Wrap5 {
|
||||
mut:
|
||||
data Tiny5
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
struct Tiny6 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
}
|
||||
|
||||
struct Wrap6 {
|
||||
mut:
|
||||
data Tiny6
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
struct Tiny7 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
}
|
||||
|
||||
struct Wrap7 {
|
||||
mut:
|
||||
data Tiny7
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
fn check3() bool {
|
||||
src := Tiny3{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
}
|
||||
mut w := Wrap3{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
}
|
||||
|
||||
fn check5() bool {
|
||||
src := Tiny5{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
}
|
||||
mut w := Wrap5{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
&& w.data.d == 4 && w.data.e == 5
|
||||
}
|
||||
|
||||
fn check6() bool {
|
||||
src := Tiny6{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
}
|
||||
mut w := Wrap6{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
&& w.data.d == 4 && w.data.e == 5 && w.data.f == 6
|
||||
}
|
||||
|
||||
fn check7() bool {
|
||||
src := Tiny7{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
g: 7
|
||||
}
|
||||
mut w := Wrap7{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
&& w.data.d == 4 && w.data.e == 5 && w.data.f == 6 && w.data.g == 7
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if check3() && check5() && check6() && check7() {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_small_aggregates_pass_by_value_in_registers() {
|
||||
output := run_x64_backend_runtime_program('small_aggregate_reg_args', "module main
|
||||
|
||||
struct Tiny3 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
}
|
||||
|
||||
struct Tiny5 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
}
|
||||
|
||||
struct Tiny6 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
}
|
||||
|
||||
struct Tiny7 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
}
|
||||
|
||||
fn sum3(t Tiny3) int {
|
||||
return int(t.a) + int(t.b) + int(t.c)
|
||||
}
|
||||
|
||||
fn sum5(t Tiny5) int {
|
||||
return int(t.a) + int(t.b) + int(t.c) + int(t.d) + int(t.e)
|
||||
}
|
||||
|
||||
fn sum6(t Tiny6) int {
|
||||
return int(t.a) + int(t.b) + int(t.c) + int(t.d) + int(t.e) + int(t.f)
|
||||
}
|
||||
|
||||
fn sum7(t Tiny7) int {
|
||||
return int(t.a) + int(t.b) + int(t.c) + int(t.d) + int(t.e) + int(t.f) + int(t.g)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := Tiny3{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
}
|
||||
b := Tiny5{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
}
|
||||
c := Tiny6{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
}
|
||||
d := Tiny7{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
g: 7
|
||||
}
|
||||
if sum3(a) == 6 && sum5(b) == 15 && sum6(c) == 21 && sum7(d) == 28 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_raw_aggregate_arg_does_not_clobber_rcx_arg() {
|
||||
output := run_x64_backend_runtime_program('small_aggregate_after_rcx_arg', "module main
|
||||
|
||||
struct Tiny3 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
}
|
||||
|
||||
fn check(a i64, b i64, c i64, marker i64, t Tiny3) i64 {
|
||||
return marker + i64(t.a) + i64(t.b) + i64(t.c)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
t := Tiny3{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
}
|
||||
if check(10, 20, 30, 1000, t) == 1006 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_big17_sret_and_indirect_param_copy_exact_bytes() {
|
||||
output := run_x64_backend_runtime_program('big17_exact_copies', "module main
|
||||
|
||||
struct Big17 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
h u8
|
||||
i u8
|
||||
j u8
|
||||
k u8
|
||||
l u8
|
||||
m u8
|
||||
n u8
|
||||
o u8
|
||||
p u8
|
||||
q u8
|
||||
}
|
||||
|
||||
fn make_big() Big17 {
|
||||
return Big17{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
g: 7
|
||||
h: 8
|
||||
i: 9
|
||||
j: 10
|
||||
k: 11
|
||||
l: 12
|
||||
m: 13
|
||||
n: 14
|
||||
o: 15
|
||||
p: 16
|
||||
q: 17
|
||||
}
|
||||
}
|
||||
|
||||
fn id_big(x Big17) Big17 {
|
||||
return x
|
||||
}
|
||||
|
||||
fn sum_big(x Big17) int {
|
||||
return int(x.a) + int(x.b) + int(x.c) + int(x.d) + int(x.e) + int(x.f) + int(x.g)
|
||||
+ int(x.h) + int(x.i) + int(x.j) + int(x.k) + int(x.l) + int(x.m) + int(x.n)
|
||||
+ int(x.o) + int(x.p) + int(x.q)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := make_big()
|
||||
y := id_big(x)
|
||||
if sum_big(y) == 153 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_fixed_array_store_uses_raw_copy() {
|
||||
output := run_x64_backend_runtime_program('fixed_array_store', "module main
|
||||
|
||||
fn main() {
|
||||
mut src := [3]u8{}
|
||||
src[0] = 1
|
||||
src[1] = 2
|
||||
src[2] = 3
|
||||
mut dst := [3]u8{}
|
||||
dst = src
|
||||
if dst[0] == 1 && dst[1] == 2 && dst[2] == 3 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_heap_sizing_allows_struct_access_past_eight_bytes() {
|
||||
output := run_x64_backend_runtime_program('heap_struct_size', "module main
|
||||
|
||||
struct Heap17 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
h u8
|
||||
i u8
|
||||
j u8
|
||||
k u8
|
||||
l u8
|
||||
m u8
|
||||
n u8
|
||||
o u8
|
||||
p u8
|
||||
q u8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut p := &Heap17{}
|
||||
p.q = 17
|
||||
if p.q == 17 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_float_width_casts_convert_values() {
|
||||
output := run_x64_backend_runtime_program('float_width_casts', "module main
|
||||
|
||||
fn widen(x f32) f64 {
|
||||
return f64(x)
|
||||
}
|
||||
|
||||
fn narrow(x f64) f32 {
|
||||
return f32(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := widen(f32(1.5))
|
||||
b := narrow(a + 0.25)
|
||||
if a > 1.49 && a < 1.51 && b > f32(1.74) && b < f32(1.76) {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_scalar_float_abi_uses_xmm_registers() {
|
||||
output := run_x64_backend_runtime_program('scalar_float_abi', "module main
|
||||
|
||||
fn from_f64_u32(x f64) u32 {
|
||||
return u32(x)
|
||||
}
|
||||
|
||||
fn id_f64(x f64) f64 {
|
||||
return x
|
||||
}
|
||||
|
||||
fn id_f32(x f32) f32 {
|
||||
return x
|
||||
}
|
||||
|
||||
fn mixed(a i64, x f64, b i64, y f32, c i64) i64 {
|
||||
if x > 1.24 && x < 1.26 && y > f32(2.49) && y < f32(2.51) {
|
||||
return a + b + c
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := 4000000000.0
|
||||
local := u32(x) == u32(4000000000)
|
||||
low := from_f64_u32(42.0) == u32(42)
|
||||
high := from_f64_u32(4000000000.0) == u32(4000000000)
|
||||
ret64 := id_f64(1.25) > 1.24 && id_f64(1.25) < 1.26
|
||||
ret32 := id_f32(f32(2.5)) > f32(2.49) && id_f32(f32(2.5)) < f32(2.51)
|
||||
mixed_ok := mixed(10, 1.25, 20, f32(2.5), 30) == 60
|
||||
if local && low && high && ret64 && ret32 && mixed_ok {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_scalar_float_stack_args_fail_explicitly() {
|
||||
output := run_x64_backend_runtime_compile_error('float_stack_arg_unsupported', 'module main
|
||||
|
||||
fn many(a f64, b f64, c f64, d f64, e f64, f f64, g f64, h f64, i f64) f64 {
|
||||
return a + b + c + d + e + f + g + h + i
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = many(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)
|
||||
}
|
||||
')
|
||||
assert output.contains('x64: unsupported backend feature: stack-passed float parameter')
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_float_comparisons_use_numeric_semantics() {
|
||||
output := run_x64_backend_runtime_program('float_comparisons', "module main
|
||||
|
||||
import os
|
||||
|
||||
fn eq_f64(a f64, b f64) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
fn ne_f64(a f64, b f64) bool {
|
||||
return a != b
|
||||
}
|
||||
|
||||
fn lt_f64(a f64, b f64) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
fn gt_f64(a f64, b f64) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
fn le_f64(a f64, b f64) bool {
|
||||
return a <= b
|
||||
}
|
||||
|
||||
fn ge_f64(a f64, b f64) bool {
|
||||
return a >= b
|
||||
}
|
||||
|
||||
fn le_f32(a f32, b f32) bool {
|
||||
return a <= b
|
||||
}
|
||||
|
||||
fn gt_f32(a f32, b f32) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
fn ge_f32(a f32, b f32) bool {
|
||||
return a >= b
|
||||
}
|
||||
|
||||
fn eq_f32(a f32, b f32) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
fn ne_f32(a f32, b f32) bool {
|
||||
return a != b
|
||||
}
|
||||
|
||||
fn lt_f32(a f32, b f32) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
argc := os.args.len
|
||||
z := f64(argc - argc)
|
||||
one := f64(argc)
|
||||
two := one + one
|
||||
neg_zero := -z
|
||||
nan := z / z
|
||||
ok64 := eq_f64(z, neg_zero) && ne_f64(nan, nan) && lt_f64(-two, -one)
|
||||
&& gt_f64(-one, -two) && le_f64(z, neg_zero) && ge_f64(z, neg_zero)
|
||||
nan64 := !eq_f64(nan, nan) && ne_f64(nan, nan) && !lt_f64(nan, one)
|
||||
&& !le_f64(nan, one) && !gt_f64(nan, one) && !ge_f64(nan, one)
|
||||
nanf := f32(nan)
|
||||
onef := f32(one)
|
||||
ok32 := eq_f32(f32(z), f32(neg_zero)) && lt_f32(f32(-two), f32(-one))
|
||||
nan32 := !eq_f32(nanf, nanf) && ne_f32(nanf, nanf) && !lt_f32(nanf, onef)
|
||||
&& !le_f32(nanf, onef) && !gt_f32(nanf, onef) && !ge_f32(nanf, onef)
|
||||
if ok64 && nan64 && ok32 && nan32 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_unsigned_64_to_float_handles_high_bit() {
|
||||
output := run_x64_backend_runtime_program('u64_to_float_high_bit', "module main
|
||||
|
||||
fn to_f64(x u64) f64 {
|
||||
return f64(x)
|
||||
}
|
||||
|
||||
fn to_f32(x u64) f32 {
|
||||
return f32(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := to_f64(u64(9223372036854775808))
|
||||
b := to_f64(u64(18446744073709551615))
|
||||
c := to_f32(u64(9223372036854775808))
|
||||
if a == 9223372036854775808.0 && b > 18446744073709550000.0 && c > f32(9223371000000000000.0) {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_float_to_unsigned_64_handles_high_bit() {
|
||||
output := run_x64_backend_runtime_program('float_to_u64_high_bit', "module main
|
||||
|
||||
fn from_f64(x f64) u64 {
|
||||
return u64(x)
|
||||
}
|
||||
|
||||
fn from_f32(x f32) u64 {
|
||||
return u64(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := from_f64(9223372036854775808.0)
|
||||
b := from_f64(9223372036854777856.0)
|
||||
c := from_f32(f32(9223372036854775808.0))
|
||||
if a == u64(9223372036854775808) && b == u64(9223372036854777856)
|
||||
&& c == u64(9223372036854775808) {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_aggregate_arg_spills_to_stack() {
|
||||
output := run_x64_backend_runtime_program('aggregate_arg_spill', "module main
|
||||
|
||||
struct Pair {
|
||||
a i64
|
||||
b i64
|
||||
}
|
||||
|
||||
fn sum_after_five(a i64, b i64, c i64, d i64, e i64, p Pair) i64 {
|
||||
return a + b + c + d + e + p.a + p.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum_after_five(1, 2, 3, 4, 5, Pair{6, 7}) == 28 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_stack_arg_after_two_chunk_aggregate() {
|
||||
output := run_x64_backend_runtime_program('stack_arg_after_pair', "module main
|
||||
|
||||
struct Pair {
|
||||
a i64
|
||||
b i64
|
||||
}
|
||||
|
||||
fn sum_pair_then_scalars(p Pair, a i64, b i64, c i64, d i64, e i64) i64 {
|
||||
return p.a + p.b + a + b + c + d + e
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum_pair_then_scalars(Pair{10, 20}, 1, 2, 3, 4, 5) == 45 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_spilled_aggregate_does_not_consume_remaining_register() {
|
||||
output := run_x64_backend_runtime_program('aggregate_spill_then_reg', "module main
|
||||
|
||||
struct Pair {
|
||||
a i64
|
||||
b i64
|
||||
}
|
||||
|
||||
fn sum_spill_then_reg(a i64, b i64, c i64, d i64, e i64, p Pair, z i64) i64 {
|
||||
return p.a + p.b + z
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum_spill_then_reg(1, 2, 3, 4, 5, Pair{10, 20}, 6) == 36 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user