Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

File diff suppressed because it is too large. Load diff
+635
View File
@@ -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))
}
@@ -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
}
+613
View File
@@ -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)
}
@@ -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
+103
View File
@@ -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
+257
View File
@@ -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
}
+409
View File
@@ -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
+284
View File
@@ -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