Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1 @@
|
||||
*.c
|
||||
@@ -0,0 +1,59 @@
|
||||
# Run all ARM64 tests
|
||||
```
|
||||
./v run vlib/v2_toberemoved/gen/arm64/tests/run_tests.v
|
||||
|
||||
```
|
||||
# Run specific test with test_ssa_backends
|
||||
```
|
||||
cd cmd/v2
|
||||
./test_ssa_backends arm64 ../../vlib/v2_toberemoved/gen/arm64/tests/string_concat.v
|
||||
```
|
||||
|
||||
# Or directly with v2
|
||||
```
|
||||
./v2 -backend arm64 ../../vlib/v2_toberemoved/gen/arm64/tests/string_concat.v
|
||||
```
|
||||
|
||||
# Useful cleanc flags
|
||||
```
|
||||
# Disable builtin+strconv object cache for this build:
|
||||
./v2 -backend cleanc -nocache file.v
|
||||
|
||||
# Print C compiler and link command(s):
|
||||
./v2 -backend cleanc -showcc file.v
|
||||
|
||||
# Print extended parse/build stats:
|
||||
./v2 -stats -backend cleanc file.v
|
||||
# Includes per-pass C Gen timing lines to help locate slow codegen phases.
|
||||
|
||||
# Print every parsed file (full and .vh groups):
|
||||
./v2 -print-parsed-files -backend cleanc file.v
|
||||
|
||||
# Disable markused pruning (emit all functions):
|
||||
./v2 -nomarkused -backend cleanc file.v
|
||||
```
|
||||
|
||||
# Eval backend
|
||||
```
|
||||
# Execute a small V program directly from the v2 AST:
|
||||
./v2 -eval file.v
|
||||
|
||||
# Pass runtime args to the interpreted program:
|
||||
./v2 -eval file.v -- arg1 arg2
|
||||
|
||||
# Self-host under eval without recursively reusing the outer compiler flags:
|
||||
./v2 -eval cmd/v2/v2.v -- -o /tmp/v2self cmd/v2/v2.v
|
||||
|
||||
# Current scope: literals, locals/consts, plain fn calls, if/for/range,
|
||||
# arrays, indexing, len, println/print, and basic string interpolation.
|
||||
```
|
||||
|
||||
# SSA C backend (restored)
|
||||
```
|
||||
# Run the SSA -> C backend directly:
|
||||
./v2 -backend c cmd/v2/test.v
|
||||
|
||||
# Compare SSA C output against reference V output:
|
||||
cd cmd/v2
|
||||
../../v run test_ssa_backends.v c
|
||||
```
|
||||
@@ -0,0 +1,346 @@
|
||||
// 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 main
|
||||
|
||||
import gg
|
||||
import v2.profiler
|
||||
|
||||
// draw_header renders the top stats bar
|
||||
fn draw_header(mut app App) {
|
||||
ctx := app.gg
|
||||
w := f32(ctx.width)
|
||||
|
||||
// Background
|
||||
ctx.draw_rect_filled(0, 0, w, f32(app.header_height), header_bg)
|
||||
|
||||
// Get stats
|
||||
stats := app.cached_stats
|
||||
|
||||
// Format and draw stats
|
||||
heap_str := 'HEAP: ${profiler.format_bytes(stats.live_bytes)}'
|
||||
peak_str := 'Peak: ${profiler.format_bytes(stats.peak_bytes)}'
|
||||
allocs_str := '${stats.total_allocs} allocs'
|
||||
frees_str := '${stats.total_frees} frees'
|
||||
leaks_str := '${stats.leak_count} leaks'
|
||||
|
||||
// Draw stats with spacing
|
||||
mut x := f32(20)
|
||||
y := f32(22)
|
||||
|
||||
ctx.draw_text(int(x), int(y), heap_str, color: text_color, size: 20)
|
||||
x += 180
|
||||
|
||||
ctx.draw_text(int(x), int(y), peak_str, color: text_color, size: 20)
|
||||
x += 160
|
||||
|
||||
ctx.draw_text(int(x), int(y), allocs_str, color: new_alloc_color, size: 20)
|
||||
x += 140
|
||||
|
||||
ctx.draw_text(int(x), int(y), frees_str, color: freed_color, size: 20)
|
||||
x += 120
|
||||
|
||||
// Show leaks in red if there are any
|
||||
leak_clr := if stats.leak_count > 0 { leak_color } else { text_color }
|
||||
ctx.draw_text(int(x), int(y), leaks_str, color: leak_clr, size: 20)
|
||||
|
||||
// Mode and frame counter on right
|
||||
mode_str := if app.demo_mode {
|
||||
'DEMO'
|
||||
} else if app.live_mode {
|
||||
'LIVE'
|
||||
} else {
|
||||
'LOCAL'
|
||||
}
|
||||
ctx.draw_text(int(w) - 250, int(y), mode_str, color: gg.Color{100, 255, 100, 255}, size: 20)
|
||||
|
||||
frame_str := 'Frame: ${stats.frame_count}'
|
||||
ctx.draw_text(int(w) - 150, int(y), frame_str, color: text_color, size: 20)
|
||||
|
||||
// Separator line
|
||||
ctx.draw_line(0, f32(app.header_height), w, f32(app.header_height), grid_color)
|
||||
}
|
||||
|
||||
// draw_histogram renders the allocation histogram
|
||||
fn draw_histogram(mut app App) {
|
||||
ctx := app.gg
|
||||
w := f32(ctx.width)
|
||||
|
||||
// Histogram area
|
||||
hist_y := f32(app.header_height)
|
||||
hist_h := f32(app.histogram_height)
|
||||
center_y := hist_y + hist_h / 2
|
||||
|
||||
// Background
|
||||
ctx.draw_rect_filled(0, hist_y, w, hist_h, bg_color)
|
||||
|
||||
// Center line
|
||||
ctx.draw_line(0, center_y, w, center_y, grid_color)
|
||||
|
||||
// Get frame data (always use cached data - it's populated by update_cache from snapshot or demo)
|
||||
frames := app.cached_frames
|
||||
if frames.len == 0 {
|
||||
ctx.draw_text(int(w / 2) - 80, int(center_y) - 10, 'No frame data',
|
||||
color: text_color
|
||||
size: 18
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate max values for scaling
|
||||
mut max_new := u64(1)
|
||||
mut max_freed := u64(1)
|
||||
for frame in frames {
|
||||
if frame.new_bytes > max_new {
|
||||
max_new = frame.new_bytes
|
||||
}
|
||||
if frame.freed_bytes > max_freed {
|
||||
max_freed = frame.freed_bytes
|
||||
}
|
||||
}
|
||||
|
||||
// Bar dimensions
|
||||
margin := f32(40)
|
||||
bar_area_width := w - margin * 2
|
||||
bar_width := bar_area_width / f32(frames.len)
|
||||
if bar_width < 1 {
|
||||
return
|
||||
}
|
||||
bar_gap := bar_width * 0.1
|
||||
actual_bar_w := bar_width - bar_gap
|
||||
|
||||
max_bar_height := (hist_h / 2) - 10
|
||||
|
||||
// Draw bars for each frame
|
||||
for i, frame in frames {
|
||||
x := margin + f32(i) * bar_width
|
||||
|
||||
// New allocations (cyan, above center)
|
||||
if frame.new_bytes > 0 {
|
||||
new_height := f32(frame.new_bytes) / f32(max_new) * max_bar_height
|
||||
bar_color := if i == app.selected_frame { selected_color } else { new_alloc_color }
|
||||
ctx.draw_rect_filled(x, center_y - new_height, actual_bar_w, new_height, bar_color)
|
||||
}
|
||||
|
||||
// Freed allocations (purple, below center)
|
||||
if frame.freed_bytes > 0 {
|
||||
freed_height := f32(frame.freed_bytes) / f32(max_freed) * max_bar_height
|
||||
bar_color := if i == app.selected_frame {
|
||||
gg.Color{150, 130, 255, 200}
|
||||
} else {
|
||||
freed_color
|
||||
}
|
||||
ctx.draw_rect_filled(x, center_y, actual_bar_w, freed_height, bar_color)
|
||||
}
|
||||
|
||||
// Hover highlight
|
||||
if i == app.hover_frame && i != app.selected_frame {
|
||||
ctx.draw_rect_empty(x - 1, hist_y + 5, actual_bar_w + 2, hist_h - 10, timeline_cursor)
|
||||
}
|
||||
}
|
||||
|
||||
// Legend
|
||||
ctx.draw_rect_filled(10, hist_y + 10, 12, 12, new_alloc_color)
|
||||
ctx.draw_text(26, int(hist_y) + 8, 'New', color: text_color, size: 14)
|
||||
|
||||
ctx.draw_rect_filled(70, hist_y + 10, 12, 12, freed_color)
|
||||
ctx.draw_text(86, int(hist_y) + 8, 'Freed', color: text_color, size: 14)
|
||||
|
||||
// Separator line
|
||||
ctx.draw_line(0, hist_y + hist_h, w, hist_y + hist_h, grid_color)
|
||||
}
|
||||
|
||||
// draw_timeline renders the frame timeline with scrubber
|
||||
fn draw_timeline(mut app App) {
|
||||
ctx := app.gg
|
||||
w := f32(ctx.width)
|
||||
|
||||
// Timeline area
|
||||
timeline_y := f32(app.header_height + app.histogram_height)
|
||||
timeline_h := f32(app.timeline_height)
|
||||
|
||||
// Background
|
||||
ctx.draw_rect_filled(0, timeline_y, w, timeline_h, header_bg)
|
||||
|
||||
frames := app.cached_frames
|
||||
if frames.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Draw frame ticks
|
||||
margin := f32(40)
|
||||
tick_area_width := w - margin * 2
|
||||
tick_spacing := tick_area_width / f32(frames.len)
|
||||
|
||||
// Draw tick marks
|
||||
for i := 0; i < frames.len; i++ {
|
||||
x := margin + f32(i) * tick_spacing
|
||||
tick_height := if i % 10 == 0 { f32(15) } else { f32(8) }
|
||||
ctx.draw_line(x, timeline_y + 20, x, timeline_y + 20 + tick_height, grid_color)
|
||||
|
||||
// Frame number labels every 10 frames
|
||||
if i % 10 == 0 && tick_spacing > 3 {
|
||||
ctx.draw_text(int(x) - 10, int(timeline_y) + 40, '${i}', color: text_color, size: 12)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selected frame cursor
|
||||
if app.selected_frame >= 0 && app.selected_frame < frames.len {
|
||||
cursor_x := margin + f32(app.selected_frame) * tick_spacing
|
||||
// Triangle cursor
|
||||
draw_triangle_filled(ctx, cursor_x, timeline_y + 15, cursor_x - 8, timeline_y + 5,
|
||||
|
||||
cursor_x + 8, timeline_y + 5, timeline_cursor)
|
||||
|
||||
// Vertical line
|
||||
ctx.draw_line(cursor_x, timeline_y + 15, cursor_x, timeline_y + timeline_h - 5,
|
||||
timeline_cursor)
|
||||
|
||||
// Frame info
|
||||
frame := frames[app.selected_frame]
|
||||
info := 'Frame ${app.selected_frame}: +${profiler.format_bytes(frame.new_bytes)} / -${profiler.format_bytes(frame.freed_bytes)}'
|
||||
ctx.draw_text(int(w / 2) - 100, int(timeline_y) + 55, info, color: timeline_cursor, size: 16)
|
||||
}
|
||||
|
||||
// Separator line
|
||||
ctx.draw_line(0, timeline_y + timeline_h, w, timeline_y + timeline_h, grid_color)
|
||||
}
|
||||
|
||||
// draw_controls renders the filter controls
|
||||
fn draw_controls(mut app App) {
|
||||
ctx := app.gg
|
||||
w := f32(ctx.width)
|
||||
|
||||
// Controls area
|
||||
ctrl_y := f32(app.header_height + app.histogram_height + app.timeline_height)
|
||||
ctrl_h := f32(app.controls_height)
|
||||
|
||||
// Background
|
||||
ctx.draw_rect_filled(0, ctrl_y, w, ctrl_h, bg_color)
|
||||
|
||||
// Filter buttons
|
||||
mut x := f32(20)
|
||||
y := ctrl_y + 10
|
||||
|
||||
// Filter label
|
||||
ctx.draw_text(int(x), int(y), 'Filter:', color: text_color, size: 16)
|
||||
x += 60
|
||||
|
||||
// Filter mode buttons
|
||||
filters := ['All', 'Leaks', 'Large', 'Frame']
|
||||
modes := [FilterMode.all, FilterMode.new_not_freed, FilterMode.large, FilterMode.current_frame]
|
||||
|
||||
for i, label in filters {
|
||||
btn_w := f32(60)
|
||||
btn_h := f32(24)
|
||||
|
||||
// Highlight active filter
|
||||
btn_color := if app.filter_mode == modes[i] { selected_color } else { header_bg }
|
||||
ctx.draw_rect_filled(x, y - 2, btn_w, btn_h, btn_color)
|
||||
ctx.draw_rect_empty(x, y - 2, btn_w, btn_h, grid_color)
|
||||
|
||||
ctx.draw_text(int(x) + 8, int(y), label, color: text_color, size: 14)
|
||||
x += btn_w + 10
|
||||
}
|
||||
|
||||
// Separator line
|
||||
ctx.draw_line(0, ctrl_y + ctrl_h, w, ctrl_y + ctrl_h, grid_color)
|
||||
}
|
||||
|
||||
// draw_details renders the selection details panel
|
||||
fn draw_details(mut app App) {
|
||||
ctx := app.gg
|
||||
w := f32(ctx.width)
|
||||
h := f32(ctx.height)
|
||||
|
||||
// Details area
|
||||
details_y := f32(app.header_height + app.histogram_height + app.timeline_height +
|
||||
app.controls_height)
|
||||
details_h := h - details_y
|
||||
|
||||
// Background
|
||||
ctx.draw_rect_filled(0, details_y, w, details_h, header_bg)
|
||||
|
||||
// Show selected allocation info or instructions
|
||||
if app.selected_alloc >= 0 {
|
||||
allocs := app.cached_allocs
|
||||
if app.selected_alloc < allocs.len {
|
||||
alloc := allocs[app.selected_alloc]
|
||||
|
||||
// Format allocation info
|
||||
ptr_str := 'Address: 0x${voidptr(alloc.ptr):p}'
|
||||
size_str := 'Size: ${profiler.format_bytes(u64(alloc.size))} (${alloc.size} bytes)'
|
||||
frame_str := 'Allocated: frame ${alloc.frame}'
|
||||
status_str := if alloc.freed {
|
||||
'Status: FREED (frame ${alloc.free_frame})'
|
||||
} else {
|
||||
'Status: LIVE (potential leak)'
|
||||
}
|
||||
|
||||
status_color := if alloc.freed { freed_color } else { leak_color }
|
||||
|
||||
mut y := details_y + 15
|
||||
ctx.draw_text(20, int(y), ptr_str, color: text_color, size: 16)
|
||||
|
||||
ctx.draw_text(250, int(y), size_str, color: text_color, size: 16)
|
||||
|
||||
ctx.draw_text(500, int(y), frame_str, color: text_color, size: 16)
|
||||
|
||||
ctx.draw_text(700, int(y), status_str, color: status_color, size: 16)
|
||||
|
||||
// Source location if available
|
||||
if alloc.file.len > 0 {
|
||||
y += 25
|
||||
loc_str := 'Source: ${alloc.file}:${alloc.line}'
|
||||
ctx.draw_text(20, int(y), loc_str, color: new_alloc_color, size: 16)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Instructions
|
||||
ctx.draw_text(20, int(details_y) + 20,
|
||||
'Click on a histogram bar to select a frame, or use keyboard arrows to navigate',
|
||||
color: text_color
|
||||
size: 16
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// draw_frame is the main frame callback
|
||||
pub fn draw_frame(mut app App) {
|
||||
if app.gg == unsafe { nil } {
|
||||
return
|
||||
}
|
||||
app.gg.begin()
|
||||
|
||||
// Update data
|
||||
if app.demo_mode {
|
||||
update_demo_data(mut app)
|
||||
} else {
|
||||
app.update_cache()
|
||||
}
|
||||
|
||||
// Draw all sections
|
||||
draw_header(mut app)
|
||||
draw_histogram(mut app)
|
||||
draw_timeline(mut app)
|
||||
draw_controls(mut app)
|
||||
draw_details(mut app)
|
||||
|
||||
// Draw status message if in live mode with no data
|
||||
if app.live_mode && app.cached_frames.len == 0 {
|
||||
ctx := app.gg
|
||||
w := f32(ctx.width)
|
||||
h := f32(ctx.height)
|
||||
ctx.draw_text(int(w / 2) - 200, int(h / 2), app.status_msg,
|
||||
color: text_color
|
||||
size: 20
|
||||
)
|
||||
}
|
||||
|
||||
app.gg.end()
|
||||
}
|
||||
|
||||
// Helper: draw a filled triangle
|
||||
fn draw_triangle_filled(ctx &gg.Context, x1 f32, y1 f32, x2 f32, y2 f32, x3 f32, y3 f32, c gg.Color) {
|
||||
ctx.draw_convex_poly([x1, y1, x2, y2, x3, y3], c)
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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 main
|
||||
|
||||
import gg
|
||||
import os
|
||||
import v2.profiler
|
||||
|
||||
// handle_event processes user input events
|
||||
pub fn handle_event(e &gg.Event, mut app App) {
|
||||
match e.typ {
|
||||
.mouse_down {
|
||||
handle_mouse_click(e, mut app)
|
||||
}
|
||||
.mouse_move {
|
||||
handle_mouse_move(e, mut app)
|
||||
}
|
||||
.key_down {
|
||||
handle_key_down(e, mut app)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
// handle_mouse_click processes mouse click events
|
||||
fn handle_mouse_click(e &gg.Event, mut app App) {
|
||||
x := e.mouse_x
|
||||
y := e.mouse_y
|
||||
|
||||
// Check which region was clicked
|
||||
hist_y := f32(app.header_height)
|
||||
hist_h := f32(app.histogram_height)
|
||||
timeline_y := hist_y + hist_h
|
||||
timeline_h := f32(app.timeline_height)
|
||||
ctrl_y := timeline_y + timeline_h
|
||||
ctrl_h := f32(app.controls_height)
|
||||
|
||||
// Click in histogram area
|
||||
if y >= hist_y && y < hist_y + hist_h {
|
||||
handle_histogram_click(x, y, mut app)
|
||||
return
|
||||
}
|
||||
|
||||
// Click in timeline area
|
||||
if y >= timeline_y && y < timeline_y + timeline_h {
|
||||
handle_timeline_click(x, y, mut app)
|
||||
return
|
||||
}
|
||||
|
||||
// Click in controls area
|
||||
if y >= ctrl_y && y < ctrl_y + ctrl_h {
|
||||
handle_controls_click(x, y, mut app)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle_histogram_click selects a frame from the histogram
|
||||
fn handle_histogram_click(x f32, y f32, mut app App) {
|
||||
frames := app.cached_frames
|
||||
if frames.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w := f32(app.gg.width)
|
||||
margin := f32(40)
|
||||
bar_area_width := w - margin * 2
|
||||
|
||||
// Calculate frame index
|
||||
relative_x := x - margin
|
||||
if relative_x < 0 || relative_x > bar_area_width {
|
||||
return
|
||||
}
|
||||
|
||||
bar_width := bar_area_width / f32(frames.len)
|
||||
frame_idx := int(relative_x / bar_width)
|
||||
|
||||
if frame_idx >= 0 && frame_idx < frames.len {
|
||||
app.selected_frame = frame_idx
|
||||
// Select first allocation in this frame
|
||||
allocs := app.cached_allocs
|
||||
for i, alloc in allocs {
|
||||
if alloc.frame == u64(frame_idx) {
|
||||
app.selected_alloc = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle_timeline_click scrubs the timeline
|
||||
fn handle_timeline_click(x f32, y f32, mut app App) {
|
||||
// Same logic as histogram click for frame selection
|
||||
handle_histogram_click(x, y, mut app)
|
||||
}
|
||||
|
||||
// handle_controls_click handles filter button clicks
|
||||
fn handle_controls_click(x f32, y f32, mut app App) {
|
||||
// Filter buttons start at x=80
|
||||
btn_start := f32(80)
|
||||
btn_w := f32(60)
|
||||
btn_gap := f32(10)
|
||||
|
||||
relative_x := x - btn_start
|
||||
if relative_x < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
btn_idx := int(relative_x / (btn_w + btn_gap))
|
||||
modes := [FilterMode.all, FilterMode.new_not_freed, FilterMode.large, FilterMode.current_frame]
|
||||
|
||||
if btn_idx >= 0 && btn_idx < modes.len {
|
||||
app.filter_mode = modes[btn_idx]
|
||||
}
|
||||
}
|
||||
|
||||
// handle_mouse_move updates hover state
|
||||
fn handle_mouse_move(e &gg.Event, mut app App) {
|
||||
x := e.mouse_x
|
||||
y := e.mouse_y
|
||||
|
||||
hist_y := f32(app.header_height)
|
||||
hist_h := f32(app.histogram_height)
|
||||
|
||||
// Check if hovering over histogram
|
||||
if y >= hist_y && y < hist_y + hist_h {
|
||||
frames := app.cached_frames
|
||||
if frames.len > 0 {
|
||||
w := f32(app.gg.width)
|
||||
margin := f32(40)
|
||||
bar_area_width := w - margin * 2
|
||||
relative_x := x - margin
|
||||
|
||||
if relative_x >= 0 && relative_x <= bar_area_width {
|
||||
bar_width := bar_area_width / f32(frames.len)
|
||||
app.hover_frame = int(relative_x / bar_width)
|
||||
if app.hover_frame >= frames.len {
|
||||
app.hover_frame = -1
|
||||
}
|
||||
} else {
|
||||
app.hover_frame = -1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.hover_frame = -1
|
||||
}
|
||||
}
|
||||
|
||||
// handle_key_down processes keyboard input
|
||||
fn handle_key_down(e &gg.Event, mut app App) {
|
||||
match e.key_code {
|
||||
.left {
|
||||
// Previous frame
|
||||
if app.selected_frame > 0 {
|
||||
app.selected_frame--
|
||||
}
|
||||
}
|
||||
.right {
|
||||
// Next frame
|
||||
frames := app.cached_frames
|
||||
if app.selected_frame < frames.len - 1 {
|
||||
app.selected_frame++
|
||||
}
|
||||
}
|
||||
.up {
|
||||
// Previous allocation
|
||||
if app.selected_alloc > 0 {
|
||||
app.selected_alloc--
|
||||
}
|
||||
}
|
||||
.down {
|
||||
// Next allocation
|
||||
allocs := app.cached_allocs
|
||||
if app.selected_alloc < allocs.len - 1 {
|
||||
app.selected_alloc++
|
||||
}
|
||||
}
|
||||
.enter {
|
||||
// Open source file in editor
|
||||
open_in_editor(app)
|
||||
}
|
||||
.f {
|
||||
// Cycle filter modes
|
||||
app.filter_mode = match app.filter_mode {
|
||||
.all { .new_not_freed }
|
||||
.new_not_freed { .large }
|
||||
.large { .current_frame }
|
||||
.current_frame { .all }
|
||||
}
|
||||
}
|
||||
.r {
|
||||
// Reset profiler data
|
||||
if !app.demo_mode {
|
||||
profiler.reset()
|
||||
}
|
||||
}
|
||||
.escape {
|
||||
// Clear selection
|
||||
app.selected_frame = -1
|
||||
app.selected_alloc = -1
|
||||
}
|
||||
.space {
|
||||
// Toggle demo mode
|
||||
app.demo_mode = !app.demo_mode
|
||||
if app.demo_mode {
|
||||
// Initialize demo data
|
||||
init_demo_data(mut app)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
// open_in_editor opens the selected allocation's source file in VS Code
|
||||
fn open_in_editor(app &App) {
|
||||
if app.selected_alloc < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
allocs := app.cached_allocs
|
||||
if app.selected_alloc >= allocs.len {
|
||||
return
|
||||
}
|
||||
|
||||
alloc := allocs[app.selected_alloc]
|
||||
if alloc.file.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to open in VS Code with line number
|
||||
cmd := 'code -g "${alloc.file}:${alloc.line}"'
|
||||
os.execute(cmd)
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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 main
|
||||
|
||||
import gg
|
||||
import os
|
||||
import v2.profiler
|
||||
|
||||
fn main() {
|
||||
// Initialize profiler system
|
||||
profiler.profiler_init()
|
||||
|
||||
// Create application state (on stack, like the working example)
|
||||
mut app := App{}
|
||||
|
||||
// Check command line args for mode
|
||||
args := os.args
|
||||
if '--demo' in args {
|
||||
app.demo_mode = true
|
||||
app.live_mode = false
|
||||
init_demo_data(mut app)
|
||||
} else {
|
||||
// Default to live mode - read from snapshot file
|
||||
app.demo_mode = false
|
||||
app.live_mode = true
|
||||
}
|
||||
|
||||
// Create gg context
|
||||
app.gg = gg.new_context(
|
||||
width: window_width
|
||||
height: window_height
|
||||
create_window: true
|
||||
window_title: 'V2 Allocation Profiler'
|
||||
bg_color: bg_color
|
||||
frame_fn: frame
|
||||
event_fn: event_handler
|
||||
user_data: &app
|
||||
)
|
||||
|
||||
// Run the application
|
||||
app.gg.run()
|
||||
}
|
||||
|
||||
// frame is the frame callback for gg
|
||||
fn frame(mut app App) {
|
||||
draw_frame(mut app)
|
||||
}
|
||||
|
||||
// event_handler handles gg events
|
||||
fn event_handler(e &gg.Event, mut app App) {
|
||||
handle_event(e, mut app)
|
||||
}
|
||||
|
||||
// init_demo_data populates the profiler with simulated data for demonstration
|
||||
fn init_demo_data(mut app App) {
|
||||
app.demo_mode = true
|
||||
app.demo_frame = 0
|
||||
|
||||
// Create simulated frame and allocation data
|
||||
app.cached_frames = []profiler.FrameData{cap: 100}
|
||||
app.cached_allocs = []profiler.AllocRecord{cap: 500}
|
||||
|
||||
// Simulate 100 frames of allocation activity
|
||||
mut total_live := u64(0)
|
||||
mut peak := u64(0)
|
||||
mut alloc_idx := 0
|
||||
mut total_allocs := u64(0)
|
||||
mut total_frees := u64(0)
|
||||
|
||||
for frame_num := 0; frame_num < 100; frame_num++ {
|
||||
mut frame_data := profiler.FrameData{
|
||||
frame_num: u64(frame_num)
|
||||
}
|
||||
|
||||
// Simulate some allocations each frame
|
||||
// More allocations early, fewer later (typical startup pattern)
|
||||
num_allocs := if frame_num < 20 { 10 - frame_num / 4 } else { 2 + (frame_num % 3) }
|
||||
|
||||
for _ in 0 .. num_allocs {
|
||||
// Random-ish allocation sizes
|
||||
size := 64 + ((frame_num * 17 + alloc_idx * 31) % 4096)
|
||||
|
||||
app.cached_allocs << profiler.AllocRecord{
|
||||
ptr: unsafe { voidptr(u64(0x1000) + u64(alloc_idx) * 0x100) }
|
||||
size: size
|
||||
frame: u64(frame_num)
|
||||
file: ['main.v', 'parser.v', 'scanner.v', 'types.v', 'gen.v'][alloc_idx % 5]
|
||||
line: 100 + (alloc_idx % 500)
|
||||
timestamp: i64(frame_num) * 16_666_667 + i64(alloc_idx) * 1000
|
||||
freed: false
|
||||
}
|
||||
|
||||
frame_data.new_allocs << alloc_idx
|
||||
frame_data.new_bytes += u64(size)
|
||||
total_live += u64(size)
|
||||
total_allocs++
|
||||
alloc_idx++
|
||||
}
|
||||
|
||||
// Simulate some frees (but not all - create some "leaks")
|
||||
// Free some allocations from previous frames
|
||||
if frame_num > 5 {
|
||||
num_frees := num_allocs - 1 // Always free slightly fewer than we allocate
|
||||
mut freed_count := 0
|
||||
for i := 0; i < app.cached_allocs.len && freed_count < num_frees; i++ {
|
||||
if !app.cached_allocs[i].freed && app.cached_allocs[i].frame < u64(frame_num - 3) {
|
||||
// 80% chance to free
|
||||
if (i * 7 + frame_num * 13) % 10 < 8 {
|
||||
app.cached_allocs[i].freed = true
|
||||
app.cached_allocs[i].free_frame = u64(frame_num)
|
||||
frame_data.freed_idxs << i
|
||||
frame_data.freed_bytes += u64(app.cached_allocs[i].size)
|
||||
total_live -= u64(app.cached_allocs[i].size)
|
||||
total_frees++
|
||||
freed_count++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame_data.live_bytes = total_live
|
||||
if total_live > peak {
|
||||
peak = total_live
|
||||
}
|
||||
|
||||
app.cached_frames << frame_data
|
||||
}
|
||||
|
||||
// Count leaks
|
||||
mut leak_count := 0
|
||||
for alloc in app.cached_allocs {
|
||||
if !alloc.freed {
|
||||
leak_count++
|
||||
}
|
||||
}
|
||||
|
||||
// Update stats
|
||||
app.cached_stats = profiler.Statistics{
|
||||
live_bytes: total_live
|
||||
peak_bytes: peak
|
||||
total_allocs: total_allocs
|
||||
total_frees: total_frees
|
||||
leak_count: leak_count
|
||||
frame_count: 100
|
||||
}
|
||||
}
|
||||
|
||||
// update_demo_data simulates ongoing activity in demo mode
|
||||
fn update_demo_data(mut app App) {
|
||||
// In demo mode, we just use the static simulated data
|
||||
// For a more dynamic demo, we could add new frames here
|
||||
app.demo_frame++
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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 main
|
||||
|
||||
import gg
|
||||
import v2.profiler
|
||||
|
||||
// Filter modes for allocation view
|
||||
pub enum FilterMode {
|
||||
all // Show all allocations
|
||||
new_not_freed // Show allocations that haven't been freed (potential leaks)
|
||||
large // Show allocations larger than threshold
|
||||
current_frame // Show allocations from selected frame only
|
||||
}
|
||||
|
||||
// App holds the GUI application state
|
||||
pub struct App {
|
||||
pub mut:
|
||||
gg &gg.Context = unsafe { nil }
|
||||
// View state
|
||||
selected_frame int = -1 // -1 means no selection
|
||||
selected_alloc int = -1 // Index of selected allocation
|
||||
filter_mode FilterMode = .all
|
||||
large_threshold int = 1024 // Threshold for "large" filter (bytes)
|
||||
search_text string
|
||||
// Scroll/pan state
|
||||
timeline_scroll int // Horizontal scroll for timeline
|
||||
histogram_scroll int // Scroll for histogram if needed
|
||||
// Cached data (updated each frame)
|
||||
cached_frames []profiler.FrameData
|
||||
cached_allocs []profiler.AllocRecord
|
||||
cached_stats profiler.Statistics
|
||||
// Layout dimensions
|
||||
header_height int = 60
|
||||
histogram_height int = 200
|
||||
timeline_height int = 80
|
||||
controls_height int = 40
|
||||
details_height int = 60
|
||||
// Hover state
|
||||
hover_frame int = -1
|
||||
hover_alloc int = -1
|
||||
// Mode
|
||||
demo_mode bool // Use simulated data for demo (false = live mode)
|
||||
demo_frame int // Current demo frame counter
|
||||
live_mode bool = true // Read from profiler snapshot file
|
||||
// Status
|
||||
status_msg string = 'Waiting for profiler data...'
|
||||
}
|
||||
|
||||
// Colors for the profiler visualization
|
||||
pub const bg_color = gg.Color{30, 30, 30, 255} // Dark gray background
|
||||
pub const new_alloc_color = gg.Color{0, 200, 255, 200} // Cyan for new allocations
|
||||
pub const freed_color = gg.Color{180, 100, 255, 200} // Purple for freed allocations
|
||||
pub const leak_color = gg.Color{255, 100, 100, 255} // Red for leaks/live
|
||||
pub const timeline_cursor = gg.Color{255, 255, 0, 255} // Yellow for timeline cursor
|
||||
pub const text_color = gg.Color{220, 220, 220, 255} // Light gray text
|
||||
pub const header_bg = gg.Color{40, 40, 45, 255} // Slightly lighter header
|
||||
pub const grid_color = gg.Color{60, 60, 60, 255} // Grid lines
|
||||
pub const selected_color = gg.Color{100, 150, 255, 255} // Selection highlight
|
||||
|
||||
// Window dimensions
|
||||
pub const window_width = 1200
|
||||
pub const window_height = 700
|
||||
|
||||
// init_app creates a new App instance (unused, kept for reference)
|
||||
fn init_app_heap() &App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
// update_cache refreshes cached profiler data
|
||||
pub fn (mut app App) update_cache() {
|
||||
if app.demo_mode {
|
||||
return
|
||||
}
|
||||
|
||||
if app.live_mode {
|
||||
// Read from snapshot file
|
||||
if snap := profiler.read_snapshot() {
|
||||
app.cached_frames = snap.frames
|
||||
app.cached_allocs = snap.allocs
|
||||
app.cached_stats = profiler.Statistics{
|
||||
live_bytes: snap.live_bytes
|
||||
peak_bytes: snap.peak_bytes
|
||||
total_allocs: snap.total_allocs
|
||||
total_frees: snap.total_frees
|
||||
leak_count: count_leaks(snap.allocs)
|
||||
frame_count: snap.frame_count
|
||||
}
|
||||
app.status_msg = 'Live: Frame ${snap.frame_count}'
|
||||
} else {
|
||||
app.status_msg = 'Waiting for profiler data at ${profiler.profiler_data_path}...'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// In-process mode (same process)
|
||||
app.cached_frames = profiler.get_frames()
|
||||
app.cached_allocs = profiler.get_allocs()
|
||||
app.cached_stats = profiler.get_statistics()
|
||||
}
|
||||
|
||||
fn count_leaks(allocs []profiler.AllocRecord) int {
|
||||
mut count := 0
|
||||
for alloc in allocs {
|
||||
if !alloc.freed {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// get_filtered_allocs returns allocations matching current filter
|
||||
pub fn (app &App) get_filtered_allocs() []profiler.AllocRecord {
|
||||
mut result := []profiler.AllocRecord{}
|
||||
|
||||
allocs := app.cached_allocs
|
||||
|
||||
for alloc in allocs {
|
||||
match app.filter_mode {
|
||||
.all {
|
||||
result << alloc
|
||||
}
|
||||
.new_not_freed {
|
||||
if !alloc.freed {
|
||||
result << alloc
|
||||
}
|
||||
}
|
||||
.large {
|
||||
if alloc.size >= app.large_threshold {
|
||||
result << alloc
|
||||
}
|
||||
}
|
||||
.current_frame {
|
||||
if app.selected_frame >= 0 && alloc.frame == u64(app.selected_frame) {
|
||||
result << alloc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter if text is present
|
||||
if app.search_text.len > 0 {
|
||||
mut filtered := []profiler.AllocRecord{}
|
||||
for alloc in result {
|
||||
if alloc.file.contains(app.search_text) {
|
||||
filtered << alloc
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// get_frame_at_x returns the frame index at the given x coordinate in timeline
|
||||
pub fn (app &App) get_frame_at_x(x f32, timeline_x f32, timeline_width f32) int {
|
||||
frames := app.cached_frames
|
||||
if frames.len == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Calculate which frame this x position corresponds to
|
||||
relative_x := x - timeline_x
|
||||
if relative_x < 0 || relative_x > timeline_width {
|
||||
return -1
|
||||
}
|
||||
|
||||
frame_width := timeline_width / f32(frames.len)
|
||||
frame_idx := int(relative_x / frame_width)
|
||||
|
||||
if frame_idx >= 0 && frame_idx < frames.len {
|
||||
return frame_idx
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println('hello world')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
repo_root="$(CDPATH= cd -- "${script_dir}/../.." && pwd)"
|
||||
|
||||
v1_compiler="${V:-${repo_root}/v}"
|
||||
if [ ! -x "${v1_compiler}" ]; then
|
||||
echo "FAIL: v1 compiler not found: ${v1_compiler}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "${v1_compiler}" run "${script_dir}/test_all.vsh" "$@"
|
||||
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env -S v
|
||||
|
||||
import os
|
||||
|
||||
const total_steps = 15
|
||||
const v2_bak = '/tmp/v2_src_bak_test_all'
|
||||
const cleanc_cache = '/tmp/v2_cleanc_obj_cache'
|
||||
const gitly_clone_dir = '/tmp/v2_test_gitly_clone'
|
||||
const gitly_repo_url = 'https://github.com/vlang/gitly'
|
||||
|
||||
struct Config {
|
||||
v1_compiler string
|
||||
script_dir string
|
||||
repo_root string
|
||||
v2_src string
|
||||
backend_arch string
|
||||
backend_flag string
|
||||
}
|
||||
|
||||
struct Args {
|
||||
backend_arch string
|
||||
opt_flag string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
cfg := parse_config()
|
||||
os.chdir(cfg.script_dir) or { fail('failed to enter ${cfg.script_dir}: ${err}') }
|
||||
|
||||
rm_rf(cleanc_cache)
|
||||
|
||||
section(1, '${cfg.backend_arch} self-host hello world')
|
||||
backup_v2_src(cfg)
|
||||
run('${q(cfg.v1_compiler)} -gc none -cc cc -o v2 v2.v')
|
||||
restore_v2_src(cfg)
|
||||
run('./v2 --no-parallel -backend ${cfg.backend_arch} -gc none -nocache ${cfg.backend_flag} -o v3 v2.v')
|
||||
run('./v3 --no-parallel -backend ${cfg.backend_arch} ${cfg.backend_flag} -o hello_backend hello.v')
|
||||
run('./hello_backend')
|
||||
cleanup_files(['v3', 'hello_backend'])
|
||||
|
||||
section(2, '${cfg.backend_arch} self-host chain (v2->v3->v4->v5->v6)')
|
||||
println(' Building v3 from v2...')
|
||||
run('./v2 --no-parallel -nocache -gc none -backend ${cfg.backend_arch} ${cfg.backend_flag} -o v3_chain v2.v')
|
||||
println(' Building v4 from v3...')
|
||||
run('./v3_chain --no-parallel -nocache -gc none -backend ${cfg.backend_arch} ${cfg.backend_flag} -o v4_chain v2.v')
|
||||
println(' Building v5 from v4...')
|
||||
run('./v4_chain --no-parallel -nocache -gc none -backend ${cfg.backend_arch} ${cfg.backend_flag} -o v5_chain v2.v')
|
||||
println(' Building v6 from v5...')
|
||||
run('./v5_chain --no-parallel -nocache -gc none -backend ${cfg.backend_arch} ${cfg.backend_flag} -o v6_chain v2.v')
|
||||
v5_size := os.file_size('v5_chain')
|
||||
v6_size := os.file_size('v6_chain')
|
||||
if v5_size == v6_size {
|
||||
println(' v5=v6 (${v5_size} bytes) - chain converged')
|
||||
} else {
|
||||
fail(' FAIL: v5 (${v5_size}) != v6 (${v6_size})')
|
||||
}
|
||||
cleanup_files(['v3_chain', 'v4_chain', 'v5_chain', 'v6_chain'])
|
||||
|
||||
section(3, 'Self-host test')
|
||||
run('bash test_v2_self.sh')
|
||||
|
||||
section(4, 'Builtin test files (cleanc)')
|
||||
rm_rf(cleanc_cache)
|
||||
run('./v2 ../../vlib/builtin/array_test.v')
|
||||
run('./v2 ../../vlib/builtin/string_test.v')
|
||||
run('./v2 ../../vlib/builtin/map_test.v')
|
||||
|
||||
section(5, 'Builtin test files (${cfg.backend_arch})')
|
||||
for backend_test in ['array_test.v', 'string_test.v', 'map_test.v'] {
|
||||
run('./v2 -backend ${cfg.backend_arch} ${cfg.backend_flag} "../../vlib/builtin/${backend_test}"')
|
||||
}
|
||||
|
||||
section(6, 'Math test')
|
||||
run('./v2 ../../vlib/math/math_test.v')
|
||||
|
||||
section(7, 'Math test (${cfg.backend_arch})')
|
||||
run('./v2 -backend ${cfg.backend_arch} ${cfg.backend_flag} ../../vlib/math/math_test.v')
|
||||
|
||||
section(8, 'Sumtype tests')
|
||||
for st in sumtype_tests() {
|
||||
run('./v2 ${st}')
|
||||
}
|
||||
|
||||
section(9, 'Sumtype tests (${cfg.backend_arch})')
|
||||
for st in sumtype_tests() {
|
||||
run('./v2 -backend ${cfg.backend_arch} ${cfg.backend_flag} ${st}')
|
||||
}
|
||||
|
||||
section(10, 'SSA backends test (${cfg.backend_arch})')
|
||||
run('${q(cfg.v1_compiler)} -gc none run test_ssa_backends.v ${cfg.backend_arch} ${cfg.backend_flag}')
|
||||
|
||||
section(11, 'SSA backends test (cleanc)')
|
||||
run('${q(cfg.v1_compiler)} -gc none run test_ssa_backends.v cleanc')
|
||||
|
||||
section(12, 'Transformer unit tests')
|
||||
run('${q(cfg.v1_compiler)} -gc none ../../vlib/v2_toberemoved/transformer/transformer_test.v')
|
||||
|
||||
section(13, 'Transformer integration test')
|
||||
run('${q(cfg.v1_compiler)} -gc none ../../vlib/v2_toberemoved/transformer/transformer_v2_darwin_test.v')
|
||||
|
||||
section(14, 'Cleanc runtime tests')
|
||||
run('${q(cfg.v1_compiler)} -gc none run ../../vlib/v2_toberemoved/gen/cleanc/tests/run_tests.v')
|
||||
|
||||
section(15, 'Gitly build (cleanc, -d use_openssl -d sqlite)')
|
||||
v2_bin := os.join_path(cfg.script_dir, 'v2')
|
||||
gitly_dir := resolve_gitly_dir()
|
||||
os.chdir(gitly_dir) or { fail('failed to enter ${gitly_dir}: ${err}') }
|
||||
run('${q(v2_bin)} -v -showcc -o x -stats -d use_openssl -d sqlite .')
|
||||
cleanup_files(['x'])
|
||||
os.chdir(cfg.script_dir) or { fail('failed to return to ${cfg.script_dir}: ${err}') }
|
||||
|
||||
println('')
|
||||
println('=== ALL TESTS PASSED ===')
|
||||
}
|
||||
|
||||
fn resolve_gitly_dir() string {
|
||||
local := os.join_path(os.home_dir(), 'code', 'gitly')
|
||||
if os.is_dir(local) {
|
||||
println(' Using local gitly at ${local}')
|
||||
return local
|
||||
}
|
||||
if os.is_dir(gitly_clone_dir) {
|
||||
println(' Reusing cloned gitly at ${gitly_clone_dir}')
|
||||
return gitly_clone_dir
|
||||
}
|
||||
println(' Cloning ${gitly_repo_url} into ${gitly_clone_dir}...')
|
||||
run('git clone --depth 1 ${gitly_repo_url} ${q(gitly_clone_dir)}')
|
||||
return gitly_clone_dir
|
||||
}
|
||||
|
||||
fn parse_config() Config {
|
||||
args := parse_args()
|
||||
script_dir := os.real_path(@DIR)
|
||||
repo_root := os.real_path(os.join_path(script_dir, '..', '..'))
|
||||
v1_compiler := absolute_path(os.getenv_opt('V') or { os.join_path(repo_root, 'v') })
|
||||
if !os.is_executable(v1_compiler) {
|
||||
fail('FAIL: v1 compiler not found: ${v1_compiler}')
|
||||
}
|
||||
backend_flag := args.opt_flag
|
||||
return Config{
|
||||
v1_compiler: v1_compiler
|
||||
script_dir: script_dir
|
||||
repo_root: repo_root
|
||||
v2_src: os.join_path(repo_root, 'vlib', 'v2')
|
||||
backend_arch: args.backend_arch
|
||||
backend_flag: backend_flag
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_args() Args {
|
||||
mut backend_arch := ''
|
||||
mut opt_flag := '-O0'
|
||||
mut i := 1
|
||||
for i < os.args.len {
|
||||
arg := os.args[i]
|
||||
match arg {
|
||||
'-arch', '--arch' {
|
||||
i++
|
||||
if i >= os.args.len {
|
||||
fail('missing value after -arch/--arch')
|
||||
}
|
||||
backend_arch = parse_backend_arch(os.args[i])
|
||||
}
|
||||
'-prod' {
|
||||
opt_flag = '-prod'
|
||||
}
|
||||
'-O0' {
|
||||
opt_flag = '-O0'
|
||||
}
|
||||
'-h', '--help' {
|
||||
println('Usage: v run cmd/v2/test_all.vsh [-arch|--arch auto|x64|arm64] [-prod|-O0]')
|
||||
println('')
|
||||
println('By default, backend tests use the native host architecture.')
|
||||
println('With -arch/--arch auto, backend tests use the native host architecture.')
|
||||
println('With -arch/--arch x64, backend tests use the x64 backend.')
|
||||
println('With -arch/--arch arm64, backend tests use the ARM64 backend.')
|
||||
println('With -prod, x64 and ARM64 backend builds use -prod.')
|
||||
println('With -O0, x64 and ARM64 backend builds use -O0.')
|
||||
println('With x64 backend builds, -O0 is passed through, but SSA optimize is still forced by the backend.')
|
||||
exit(0)
|
||||
}
|
||||
else {
|
||||
fail('unknown argument: ${arg}')
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
if backend_arch == '' {
|
||||
backend_arch = native_backend_arch()
|
||||
}
|
||||
return Args{
|
||||
backend_arch: backend_arch
|
||||
opt_flag: opt_flag
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_backend_arch(arch string) string {
|
||||
match arch {
|
||||
'auto' {
|
||||
return native_backend_arch()
|
||||
}
|
||||
'x64', 'arm64' {
|
||||
return arch
|
||||
}
|
||||
else {
|
||||
fail('unsupported architecture: ${arch}; expected auto, x64 or arm64')
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
fn native_backend_arch() string {
|
||||
machine := os.uname().machine.to_lower()
|
||||
match machine {
|
||||
'x86_64', 'amd64' {
|
||||
return 'x64'
|
||||
}
|
||||
'aarch64', 'arm64' {
|
||||
return 'arm64'
|
||||
}
|
||||
else {
|
||||
fail('unsupported native architecture: ${machine}; pass -arch/--arch x64 or -arch/--arch arm64')
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
fn absolute_path(path string) string {
|
||||
if os.is_abs_path(path) {
|
||||
return path
|
||||
}
|
||||
return os.join_path(os.getwd(), path)
|
||||
}
|
||||
|
||||
fn section(step int, title string) {
|
||||
if step > 1 {
|
||||
println('')
|
||||
}
|
||||
println('=== ${step}/${total_steps}: ${title} ===')
|
||||
}
|
||||
|
||||
fn backup_v2_src(cfg Config) {
|
||||
rm_rf(v2_bak)
|
||||
run('cp -R ${q(cfg.v2_src)} ${q(v2_bak)}')
|
||||
}
|
||||
|
||||
fn restore_v2_src(cfg Config) {
|
||||
run('rsync -a --delete ${q(v2_bak + '/')} ${q(cfg.v2_src + '/')}')
|
||||
}
|
||||
|
||||
fn rm_rf(path string) {
|
||||
if !os.exists(path) {
|
||||
return
|
||||
}
|
||||
if os.is_dir(path) {
|
||||
os.rmdir_all(path) or { fail('failed to remove ${path}: ${err}') }
|
||||
return
|
||||
}
|
||||
os.rm(path) or { fail('failed to remove ${path}: ${err}') }
|
||||
}
|
||||
|
||||
fn cleanup_files(paths []string) {
|
||||
for path in paths {
|
||||
os.rm(path) or {}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(cmd string) {
|
||||
code := os.system(cmd)
|
||||
if code != 0 {
|
||||
exit(code)
|
||||
}
|
||||
}
|
||||
|
||||
fn q(path string) string {
|
||||
return os.quoted_path(path)
|
||||
}
|
||||
|
||||
fn fail(message string) {
|
||||
eprintln(message)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
fn sumtype_tests() []string {
|
||||
return [
|
||||
'test_sumtype.v',
|
||||
'test_sumtype2.v',
|
||||
'test_sumtype3.v',
|
||||
'test_sumtype4.v',
|
||||
'test_sumtype_pos.v',
|
||||
'test_sumtype_data.v',
|
||||
'test_sumtype_ifexpr.v',
|
||||
'test_sumtype_nested.v',
|
||||
'test_sumtype_many.v',
|
||||
'test_sumtype_global.v',
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 main
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
t0 := time.now()
|
||||
|
||||
// Build v2 compiler
|
||||
println('[*] Building v2...')
|
||||
vroot := os.dir(@VEXE)
|
||||
v2_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v')
|
||||
v2_binary := os.join_path(vroot, 'cmd', 'v2', '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)
|
||||
}
|
||||
|
||||
// Determine backends from command line args.
|
||||
// If none are provided, run the default backend set.
|
||||
mut backends := []string{}
|
||||
if os.args.contains('cleanc') {
|
||||
backends << 'cleanc'
|
||||
}
|
||||
if os.args.contains('c') {
|
||||
backends << 'c'
|
||||
}
|
||||
if os.args.contains('arm64') {
|
||||
backends << 'arm64'
|
||||
}
|
||||
if os.args.contains('x64') {
|
||||
backends << 'x64'
|
||||
}
|
||||
if backends.len == 0 {
|
||||
backends = ['cleanc', 'c', 'arm64']
|
||||
}
|
||||
|
||||
// Parse test file from args or default to test.v
|
||||
// Support: ./test_ssa_backends arm64 path/to/file.v
|
||||
mut input_file := 'test.v'
|
||||
for arg in os.args {
|
||||
if arg.ends_with('.v') && arg != @FILE {
|
||||
input_file = arg
|
||||
break
|
||||
}
|
||||
}
|
||||
if !os.exists(input_file) {
|
||||
eprintln('Error: ${input_file} not found')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Derive output binary name from input file
|
||||
base_name := os.file_name(input_file).replace('.v', '')
|
||||
ref_output_path := './.${base_name}_ref.out.tmp'
|
||||
gen_output_path := './.${base_name}_gen.out.tmp'
|
||||
|
||||
// Get expected output: use .out file if --skip-builtin, otherwise run reference compiler
|
||||
mut expected_out := ''
|
||||
out_file := input_file.replace('.v', '.out')
|
||||
if os.args.contains('--skip-builtin') && os.exists(out_file) {
|
||||
println('[*] Using expected output from ${out_file}')
|
||||
expected_out = os.read_file(out_file) or { '' }.trim_space().replace('\r\n', '\n')
|
||||
} else {
|
||||
// Run Reference (v run test.v)
|
||||
println('[*] Running reference: ${@VEXE} -enable-globals run ${input_file}...')
|
||||
os.rm(ref_output_path) or {}
|
||||
ref_cc := if os.user_os() == 'macos' { '-cc cc ' } else { '' }
|
||||
ref_cmd := '${@VEXE} -gc none ${ref_cc}-n -w -enable-globals run ${input_file} > ${ref_output_path} 2>&1'
|
||||
ref_res := os.execute(ref_cmd)
|
||||
ref_out := os.read_file(ref_output_path) or { '' }
|
||||
os.rm(ref_output_path) or {}
|
||||
if ref_res.exit_code != 0 {
|
||||
eprintln('Error: Reference run failed')
|
||||
eprintln(ref_out)
|
||||
exit(1)
|
||||
}
|
||||
// Normalize newlines
|
||||
expected_out = ref_out.trim_space().replace('\r\n', '\n')
|
||||
}
|
||||
|
||||
mut had_failures := false
|
||||
for backend in backends {
|
||||
backend_t0 := time.now()
|
||||
|
||||
// Run v2 with selected backend
|
||||
println('[*] Running v2 -backend ${backend} ${input_file}...')
|
||||
mut backend_flags := '-gc none -backend ${backend}'
|
||||
if backend in ['arm64', 'x64'] {
|
||||
if os.args.contains('-prod') {
|
||||
backend_flags += ' -prod'
|
||||
} else if os.args.contains('-O0') {
|
||||
backend_flags += ' -O0'
|
||||
}
|
||||
}
|
||||
if backend == 'cleanc' {
|
||||
// cleanc needs full per-run codegen for this suite right now.
|
||||
backend_flags += ' -nomarkused -nocache'
|
||||
}
|
||||
if os.args.contains('--skip-builtin') && !backend_flags.contains('--skip-builtin') {
|
||||
backend_flags += ' --skip-builtin'
|
||||
}
|
||||
v2_cmd := '${v2_binary} ${backend_flags} ${input_file} -o ${base_name}'
|
||||
v2_res := os.execute(v2_cmd)
|
||||
if v2_res.exit_code != 0 {
|
||||
eprintln('Error: v2 compilation failed for backend ${backend}')
|
||||
eprintln(v2_res.output)
|
||||
had_failures = true
|
||||
continue
|
||||
}
|
||||
println(v2_res.output)
|
||||
println('compilation took ${time.since(backend_t0)}')
|
||||
|
||||
// Save the v2-produced binary before running another backend (which would overwrite it)
|
||||
saved_binary := './${base_name}_${backend}_v2'
|
||||
os.rm(saved_binary) or {}
|
||||
if os.user_os() == 'windows' {
|
||||
os.rm('${saved_binary}.exe') or {}
|
||||
}
|
||||
os.cp('./${base_name}', saved_binary) or {
|
||||
eprintln('Error: Failed to save v2 binary for backend ${backend}')
|
||||
had_failures = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Run generated binary
|
||||
println('[*] Running generated binary (${backend})...')
|
||||
mut cmd := saved_binary
|
||||
if os.user_os() == 'windows' {
|
||||
cmd = '${saved_binary}.exe'
|
||||
}
|
||||
os.rm(gen_output_path) or {}
|
||||
// 60s timeout to catch infinite loops in ARM64-generated code
|
||||
has_timeout := os.exists('/opt/homebrew/bin/timeout') || os.exists('/usr/bin/timeout')
|
||||
has_gtimeout := os.exists('/opt/homebrew/bin/gtimeout') || os.exists('/usr/bin/gtimeout')
|
||||
timeout_cmd := if has_timeout {
|
||||
'timeout'
|
||||
} else if has_gtimeout {
|
||||
'gtimeout'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
gen_cmd := if timeout_cmd != '' {
|
||||
'${timeout_cmd} 60 ${cmd} > ${gen_output_path} 2>&1'
|
||||
} else {
|
||||
'${cmd} > ${gen_output_path} 2>&1'
|
||||
}
|
||||
gen_res := os.execute(gen_cmd)
|
||||
gen_out := os.read_file(gen_output_path) or { '' }
|
||||
os.rm(gen_output_path) or {}
|
||||
if gen_res.exit_code != 0 {
|
||||
if gen_res.exit_code == 124 || gen_res.exit_code == 142 || gen_res.exit_code == 14 {
|
||||
eprintln('Error: Execution timed out (infinite loop detected) for backend ${backend}')
|
||||
had_failures = true
|
||||
continue
|
||||
}
|
||||
println('Warning: Binary exited with code ${gen_res.exit_code}')
|
||||
}
|
||||
|
||||
// Strip terminal control characters that script command may prepend
|
||||
mut cleaned := gen_out.replace('\r\n', '\n').replace('\x04', '').replace('\x08', '')
|
||||
// Remove "^D" literal string that macOS script may add
|
||||
if cleaned.starts_with('^D') {
|
||||
cleaned = cleaned[2..]
|
||||
}
|
||||
actual_out := cleaned.trim_space()
|
||||
|
||||
// Compare
|
||||
if expected_out == actual_out {
|
||||
println('\n[SUCCESS] Backend ${backend}: outputs match!')
|
||||
continue
|
||||
}
|
||||
|
||||
had_failures = true
|
||||
println('\n[FAILURE] Backend ${backend}: outputs differ')
|
||||
expected_lines := expected_out.split('\n')
|
||||
actual_lines := actual_out.split('\n')
|
||||
|
||||
// Find first differing line
|
||||
mut first_diff := -1
|
||||
max_lines := if expected_lines.len > actual_lines.len {
|
||||
expected_lines.len
|
||||
} else {
|
||||
actual_lines.len
|
||||
}
|
||||
for i in 0 .. max_lines {
|
||||
exp := if i < expected_lines.len { expected_lines[i] } else { '<missing>' }
|
||||
act := if i < actual_lines.len { actual_lines[i] } else { '<missing>' }
|
||||
if exp != act {
|
||||
first_diff = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if first_diff >= 0 {
|
||||
context := 2
|
||||
start := if first_diff > context { first_diff - context } else { 0 }
|
||||
end := if first_diff + context + 1 < max_lines {
|
||||
first_diff + context + 1
|
||||
} else {
|
||||
max_lines
|
||||
}
|
||||
|
||||
println('\nExpected (reference compiler):')
|
||||
for i in start .. end {
|
||||
line := if i < expected_lines.len { expected_lines[i] } else { '<missing>' }
|
||||
println('${i + 1}: ${line}')
|
||||
}
|
||||
|
||||
println('\nGot (v2 ${backend}):')
|
||||
for i in start .. end {
|
||||
line := if i < actual_lines.len { actual_lines[i] } else { '<missing>' }
|
||||
println('${i + 1}: ${line}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if had_failures {
|
||||
println('\n[FAILURE] One or more backends failed')
|
||||
exit(1)
|
||||
} else {
|
||||
println('\n[SUCCESS] All requested backends passed')
|
||||
}
|
||||
println('total time ${time.since(t0)}')
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
type Animal = Cat | Dog
|
||||
|
||||
struct Cat {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Dog {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
fn make_animal() Animal {
|
||||
return Animal(Cat{
|
||||
name: 'whiskers'
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := make_animal()
|
||||
match a {
|
||||
Cat {
|
||||
println(a.name)
|
||||
}
|
||||
Dog {
|
||||
println(a.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
struct Cat {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Dog {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
struct Bird {
|
||||
name string
|
||||
can_fly bool
|
||||
}
|
||||
|
||||
type Animal = Bird | Cat | Dog
|
||||
|
||||
fn make_cat() Animal {
|
||||
return Animal(Cat{
|
||||
name: 'whiskers'
|
||||
})
|
||||
}
|
||||
|
||||
fn make_bird() Animal {
|
||||
return Animal(Bird{
|
||||
name: 'tweety'
|
||||
can_fly: true
|
||||
})
|
||||
}
|
||||
|
||||
fn indirect() Animal {
|
||||
a := make_cat()
|
||||
return a
|
||||
}
|
||||
|
||||
fn get_name(a Animal) string {
|
||||
match a {
|
||||
Cat { return a.name }
|
||||
Dog { return a.name }
|
||||
Bird { return a.name }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := make_cat()
|
||||
b := make_bird()
|
||||
c := indirect()
|
||||
println(get_name(a))
|
||||
println(get_name(b))
|
||||
println(get_name(c))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
struct Cat {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Dog {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
struct Bird {
|
||||
name string
|
||||
can_fly bool
|
||||
}
|
||||
|
||||
type Animal = Bird | Cat | Dog
|
||||
|
||||
fn make_conditional(which int) Animal {
|
||||
result := if which == 0 {
|
||||
Animal(Cat{
|
||||
name: 'whiskers'
|
||||
})
|
||||
} else {
|
||||
Animal(Bird{
|
||||
name: 'tweety'
|
||||
can_fly: true
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fn get_name(a Animal) string {
|
||||
match a {
|
||||
Cat { return a.name }
|
||||
Dog { return a.name }
|
||||
Bird { return a.name }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := make_conditional(0)
|
||||
b := make_conditional(1)
|
||||
println(get_name(a))
|
||||
println(get_name(b))
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
struct Ident {
|
||||
name string
|
||||
line int
|
||||
col int
|
||||
}
|
||||
|
||||
struct Literal {
|
||||
value string
|
||||
}
|
||||
|
||||
type Expr = Ident | Literal
|
||||
|
||||
fn make_ident() Ident {
|
||||
return Ident{
|
||||
name: 'foo'
|
||||
line: 42
|
||||
col: 10
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_expr() Expr {
|
||||
i := make_ident()
|
||||
return i
|
||||
}
|
||||
|
||||
fn main() {
|
||||
e := wrap_expr()
|
||||
match e {
|
||||
Ident {
|
||||
println(e.name)
|
||||
println(e.line)
|
||||
println(e.col)
|
||||
}
|
||||
Literal {
|
||||
println(e.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
struct Pos {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
struct Ident {
|
||||
pos Pos
|
||||
name string
|
||||
}
|
||||
|
||||
struct Lit {
|
||||
pos Pos
|
||||
val int
|
||||
}
|
||||
|
||||
type Expr = Ident | Lit
|
||||
|
||||
fn (e Expr) get_pos() Pos {
|
||||
return match e {
|
||||
Ident { e.pos }
|
||||
Lit { e.pos }
|
||||
}
|
||||
}
|
||||
|
||||
fn make_ident() Expr {
|
||||
return Expr(Ident{
|
||||
pos: Pos{
|
||||
x: 10
|
||||
y: 20
|
||||
}
|
||||
name: 'hello'
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
e := make_ident()
|
||||
p := e.get_pos()
|
||||
println(p.x.str())
|
||||
println(p.y.str())
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
struct Pos {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
struct Ident {
|
||||
pos Pos
|
||||
name string
|
||||
}
|
||||
|
||||
struct EmptyExpr {
|
||||
dummy u8
|
||||
}
|
||||
|
||||
type Expr = EmptyExpr | Ident
|
||||
|
||||
const empty_expr = Expr(EmptyExpr{})
|
||||
|
||||
fn (e Expr) get_pos() Pos {
|
||||
return match e {
|
||||
Ident { e.pos }
|
||||
EmptyExpr { Pos{} }
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr() Expr {
|
||||
return Expr(Ident{
|
||||
pos: Pos{
|
||||
x: 42
|
||||
y: 99
|
||||
}
|
||||
name: 'macos'
|
||||
})
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
mut:
|
||||
tok int
|
||||
comptime bool
|
||||
}
|
||||
|
||||
fn (mut p Parser) expr_or_type() Expr {
|
||||
return Expr(Ident{
|
||||
pos: Pos{
|
||||
x: 77
|
||||
y: 88
|
||||
}
|
||||
name: 'linux'
|
||||
})
|
||||
}
|
||||
|
||||
fn (mut p Parser) if_expr(is_comptime bool) {
|
||||
// Exact pattern from the real parser
|
||||
mut cond := if p.tok == 42 {
|
||||
empty_expr // global constant
|
||||
} else {
|
||||
if is_comptime { p.expr_or_type() } else { p.expr_or_type() }
|
||||
}
|
||||
// This line is what crashes in v3
|
||||
guard_pos := cond.get_pos()
|
||||
println(guard_pos.x.str())
|
||||
println(guard_pos.y.str())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut parser := Parser{
|
||||
tok: 99
|
||||
comptime: true
|
||||
}
|
||||
parser.if_expr(true) // should print 77, 88
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
struct Pos {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
struct Ident {
|
||||
pos Pos
|
||||
name string
|
||||
}
|
||||
|
||||
struct EmptyExpr {
|
||||
dummy u8
|
||||
}
|
||||
|
||||
type Expr = EmptyExpr | Ident
|
||||
|
||||
fn (e Expr) get_pos() Pos {
|
||||
return match e {
|
||||
Ident { e.pos }
|
||||
EmptyExpr { Pos{} }
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr() Expr {
|
||||
return Expr(Ident{
|
||||
pos: Pos{
|
||||
x: 42
|
||||
y: 99
|
||||
}
|
||||
name: 'macos'
|
||||
})
|
||||
}
|
||||
|
||||
fn test_if_expr(is_lcbr bool) {
|
||||
mut cond := if is_lcbr {
|
||||
Expr(EmptyExpr{})
|
||||
} else {
|
||||
parse_expr()
|
||||
}
|
||||
p := cond.get_pos()
|
||||
println(p.x.str())
|
||||
println(p.y.str())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_if_expr(false) // should print 42, 99
|
||||
test_if_expr(true) // should print 0, 0
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
struct Pos {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
struct V0 {
|
||||
pos Pos
|
||||
a int
|
||||
}
|
||||
|
||||
struct V1 {
|
||||
pos Pos
|
||||
b int
|
||||
}
|
||||
|
||||
struct V2 {
|
||||
pos Pos
|
||||
c int
|
||||
}
|
||||
|
||||
struct V3 {
|
||||
pos Pos
|
||||
d int
|
||||
}
|
||||
|
||||
struct V4 {
|
||||
pos Pos
|
||||
e int
|
||||
}
|
||||
|
||||
struct V5 {
|
||||
pos Pos
|
||||
f int
|
||||
}
|
||||
|
||||
struct V6 {
|
||||
pos Pos
|
||||
g int
|
||||
}
|
||||
|
||||
struct V7 {
|
||||
pos Pos
|
||||
h int
|
||||
}
|
||||
|
||||
struct V8 {
|
||||
dummy u8
|
||||
} // EmptyExpr equivalent (tag 8)
|
||||
|
||||
struct V9 {
|
||||
pos Pos
|
||||
j int
|
||||
}
|
||||
|
||||
struct V10 {
|
||||
pos Pos
|
||||
k int
|
||||
}
|
||||
|
||||
struct V11 {
|
||||
pos Pos
|
||||
l int
|
||||
}
|
||||
|
||||
struct V12 {
|
||||
pos Pos
|
||||
m int
|
||||
}
|
||||
|
||||
struct V13 {
|
||||
pos Pos
|
||||
name string
|
||||
} // Ident equivalent (tag 13)
|
||||
|
||||
struct V14 {
|
||||
pos Pos
|
||||
n int
|
||||
}
|
||||
|
||||
type Expr = V0 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14
|
||||
|
||||
fn (e Expr) get_pos() Pos {
|
||||
return match e {
|
||||
V0 { e.pos }
|
||||
V1 { e.pos }
|
||||
V2 { e.pos }
|
||||
V3 { e.pos }
|
||||
V4 { e.pos }
|
||||
V5 { e.pos }
|
||||
V6 { e.pos }
|
||||
V7 { e.pos }
|
||||
V8 { Pos{} }
|
||||
V9 { e.pos }
|
||||
V10 { e.pos }
|
||||
V11 { e.pos }
|
||||
V12 { e.pos }
|
||||
V13 { e.pos }
|
||||
V14 { e.pos }
|
||||
}
|
||||
}
|
||||
|
||||
fn make_v13() Expr {
|
||||
return Expr(V13{
|
||||
pos: Pos{
|
||||
x: 42
|
||||
y: 99
|
||||
}
|
||||
name: 'hello'
|
||||
})
|
||||
}
|
||||
|
||||
fn test_it(is_lcbr bool, is_comptime bool) {
|
||||
mut cond := if is_lcbr {
|
||||
Expr(V8{})
|
||||
} else {
|
||||
if is_comptime { make_v13() } else { make_v13() }
|
||||
}
|
||||
p := cond.get_pos()
|
||||
println(p.x.str())
|
||||
println(p.y.str())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println('test with 15 variants, accessing V13 (tag 13):')
|
||||
test_it(false, true)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Test sum type with struct variants (not just primitives)
|
||||
// This mirrors the AST's Expr sum type pattern
|
||||
|
||||
struct BasicLit {
|
||||
kind int
|
||||
value string
|
||||
}
|
||||
|
||||
struct Ident {
|
||||
name string
|
||||
}
|
||||
|
||||
struct CallOrCast {
|
||||
lhs Expr
|
||||
expr Expr
|
||||
}
|
||||
|
||||
type Expr = BasicLit | CallOrCast | Ident
|
||||
|
||||
fn make_basic_lit(val string) Expr {
|
||||
return BasicLit{
|
||||
kind: 1
|
||||
value: val
|
||||
}
|
||||
}
|
||||
|
||||
fn make_ident(name string) Expr {
|
||||
return Ident{
|
||||
name: name
|
||||
}
|
||||
}
|
||||
|
||||
fn make_call_or_cast(lhs Expr, expr Expr) Expr {
|
||||
return CallOrCast{
|
||||
lhs: lhs
|
||||
expr: expr
|
||||
}
|
||||
}
|
||||
|
||||
fn test_basic_lit() {
|
||||
e := make_basic_lit('42')
|
||||
if e is BasicLit {
|
||||
assert e.value == '42'
|
||||
assert e.kind == 1
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_ident() {
|
||||
e := make_ident('hello')
|
||||
if e is Ident {
|
||||
assert e.name == 'hello'
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_call_or_cast() {
|
||||
lhs := make_ident('int')
|
||||
arg := make_basic_lit('0')
|
||||
e := make_call_or_cast(lhs, arg)
|
||||
if e is CallOrCast {
|
||||
if e.lhs is Ident {
|
||||
assert e.lhs.name == 'int'
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
if e.expr is BasicLit {
|
||||
assert e.expr.value == '0'
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_nested_sum_field_access() {
|
||||
// Create CallOrCast{lhs: Ident("int"), expr: BasicLit("0")}
|
||||
// This mirrors: __global g_main_argc = int(0)
|
||||
lhs := Expr(Ident{
|
||||
name: 'int'
|
||||
})
|
||||
arg := Expr(BasicLit{
|
||||
kind: 1
|
||||
value: '0'
|
||||
})
|
||||
coce := CallOrCast{
|
||||
lhs: lhs
|
||||
expr: arg
|
||||
}
|
||||
e := Expr(coce)
|
||||
if e is CallOrCast {
|
||||
// Access the nested Expr fields
|
||||
inner_expr := e.expr
|
||||
if inner_expr is BasicLit {
|
||||
assert inner_expr.value == '0'
|
||||
} else {
|
||||
eprintln('ERROR: inner_expr is not BasicLit')
|
||||
assert false
|
||||
}
|
||||
} else {
|
||||
eprintln('ERROR: e is not CallOrCast')
|
||||
assert false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
type MyUnion = int | string
|
||||
|
||||
fn (u MyUnion) tag() int {
|
||||
return match u {
|
||||
int { 0 }
|
||||
string { 1 }
|
||||
// else { -1 }
|
||||
}
|
||||
}
|
||||
|
||||
fn make_int_union() MyUnion {
|
||||
return MyUnion(42)
|
||||
}
|
||||
|
||||
fn make_string_union() MyUnion {
|
||||
return MyUnion('hello')
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := make_int_union()
|
||||
t := a.tag()
|
||||
println(t.str())
|
||||
|
||||
b := make_string_union()
|
||||
t2 := b.tag()
|
||||
println(t2.str())
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd -- "${script_dir}/../.." && pwd)"
|
||||
|
||||
v2_source="${script_dir}/v2.v"
|
||||
v2_bin="${script_dir}/v2"
|
||||
v3_bin="${script_dir}/v3"
|
||||
v4_bin="${script_dir}/v4"
|
||||
v5_bin="${script_dir}/v5"
|
||||
|
||||
# Variable for the backend (originally cleanc)
|
||||
backend="cleanc"
|
||||
|
||||
v1_compiler="${V:-${repo_root}/v}"
|
||||
if [[ ! -x "${v1_compiler}" ]]; then
|
||||
fallback_v1_compiler="${HOME}/code/v/v"
|
||||
if [[ -x "${fallback_v1_compiler}" ]]; then
|
||||
v1_compiler="${fallback_v1_compiler}"
|
||||
else
|
||||
echo "FAILURE: v1 compiler not found or not executable: ${v1_compiler}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# V1's formatter may clobber v2 source files — backup and restore.
|
||||
v2_src="${repo_root}/vlib/v2_toberemoved"
|
||||
v2_bak="/tmp/v2_src_bak_self_test"
|
||||
rm -rf "${v2_bak}"
|
||||
cp -R "${v2_src}" "${v2_bak}"
|
||||
|
||||
# Build v2 with v1.
|
||||
rm -f "${v2_bin}" "${v3_bin}" "${v3_bin}.c" "${v4_bin}" "${v4_bin}.c" "${v5_bin}" "${v5_bin}.c"
|
||||
"${v1_compiler}" -gc none -cc cc -o "${v2_bin}" "${v2_source}"
|
||||
|
||||
# Restore v2 sources after V1 build.
|
||||
rsync -a --delete "${v2_bak}/" "${v2_src}/"
|
||||
|
||||
# Use clang instead of TCC for v2-compiled C code.
|
||||
export V2CC="${V2CC:-cc}"
|
||||
|
||||
# Clear stale caches to avoid reusing incompatible generated C across runs.
|
||||
rm -rf /tmp/v2_cleanc_obj_cache
|
||||
|
||||
# Use v2 to compile itself to v3 (using defined backend).
|
||||
"${v2_bin}" -gc none -o "${v3_bin}" -backend "${backend}" "${v2_source}"
|
||||
|
||||
echo "SUCCESS: v2 successfully compiled itself to v3"
|
||||
echo "v3 binary size: $(ls -lh "${v3_bin}" | awk '{print $5}')"
|
||||
|
||||
if [[ "${backend}" != "cleanc" ]]; then
|
||||
"${v3_bin}" -gc none -o "${v4_bin}" -backend "${backend}" "${v2_source}"
|
||||
printf '\nV4 compiled\n\n'
|
||||
|
||||
"${v4_bin}" -gc none -o "${v5_bin}" -backend "${backend}" "${v2_source}"
|
||||
printf '\nV5 compiled\n\n'
|
||||
fi
|
||||
|
||||
# Test that v3 runs and produces expected output.
|
||||
output="$("${v3_bin}" 2>&1 || true)"
|
||||
expected='At least 1 .v file expected'
|
||||
|
||||
if echo "${output}" | grep -q "${expected}"; then
|
||||
echo "SUCCESS"
|
||||
else
|
||||
echo "FAILURE: Expected '${expected}' but got:"
|
||||
echo "${output}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ''
|
||||
echo '=== SELF-COMPILATION TEST PASSED ==='
|
||||
@@ -0,0 +1,310 @@
|
||||
// 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 main
|
||||
|
||||
import os
|
||||
import time
|
||||
import v2.pref
|
||||
import v2.builder
|
||||
|
||||
fn main() {
|
||||
compile_args, runtime_args := split_eval_runtime_args(os.args[1..])
|
||||
|
||||
// Check for subcommands
|
||||
if compile_args.len > 0 && compile_args[0] == 'ast' {
|
||||
run_ast_command(compile_args[1..])
|
||||
return
|
||||
}
|
||||
if compile_args.len > 0 && compile_args[0] == 'test-self' {
|
||||
run_test_self()
|
||||
return
|
||||
}
|
||||
|
||||
mut prefs := pref.new_preferences_from_args(compile_args)
|
||||
|
||||
files := get_files(compile_args)
|
||||
if files.len == 0 {
|
||||
eprintln('At least 1 .v file expected')
|
||||
exit(1)
|
||||
}
|
||||
mut eval_runtime_args := [files[0]]
|
||||
eval_runtime_args << runtime_args
|
||||
prefs.eval_runtime_args = eval_runtime_args
|
||||
|
||||
mut b := builder.new_builder(&prefs)
|
||||
b.build(files)
|
||||
|
||||
// Auto-run test binaries after compilation (matching v1 behavior)
|
||||
if prefs.output_file == '' && is_test_file(files) && should_auto_run_test_binary(&prefs) {
|
||||
last := os.file_name(files.last())
|
||||
output_name := if last.ends_with('.vv2') {
|
||||
last.all_before_last('.vv2')
|
||||
} else {
|
||||
last.all_before_last('.v')
|
||||
}
|
||||
if os.exists(output_name) {
|
||||
ret := os.system('./' + output_name)
|
||||
os.rm(output_name) or {}
|
||||
c_file := output_name + '.c'
|
||||
if !prefs.keep_c && os.exists(c_file) {
|
||||
os.rm(c_file) or {}
|
||||
}
|
||||
exit(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_eval_runtime_args(args []string) ([]string, []string) {
|
||||
for i, arg in args {
|
||||
if arg == '--' {
|
||||
return args[..i], args[i + 1..]
|
||||
}
|
||||
}
|
||||
return args, []string{}
|
||||
}
|
||||
|
||||
fn run_ast_command(args []string) {
|
||||
if args.len == 0 {
|
||||
eprintln('Usage: v2 ast <file.v>')
|
||||
eprintln('Dumps AST to <file>_ast.json and <file>_ast_transformed.json')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Find the vast2 tool relative to vexe
|
||||
vroot := os.dir(@VEXE)
|
||||
vast2_path := os.join_path(vroot, 'cmd', 'tools', 'vast2', 'vast2')
|
||||
|
||||
// Build vast2 if it doesn't exist
|
||||
if !os.exists(vast2_path) {
|
||||
eprintln('Building vast2 tool...')
|
||||
vast2_source := os.join_path(vroot, 'cmd', 'tools', 'vast2', 'vast2.v')
|
||||
build_result := os.execute('${@VEXE} ${vast2_source}')
|
||||
if build_result.exit_code != 0 {
|
||||
eprintln('Failed to build vast2 tool:')
|
||||
eprintln(build_result.output)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Run vast2 with the provided arguments
|
||||
cmd := '${vast2_path} ${args.join(' ')}'
|
||||
result := os.execute(cmd)
|
||||
print(result.output)
|
||||
if result.exit_code != 0 {
|
||||
exit(result.exit_code)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_test_file(files []string) bool {
|
||||
for file in files {
|
||||
if file.ends_with('_test.v') || file.ends_with('_test.vv2') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn should_auto_run_test_binary(prefs &pref.Preferences) bool {
|
||||
if prefs == unsafe { nil } {
|
||||
return true
|
||||
}
|
||||
if !prefs.can_run_target_binary_locally() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// get_files extracts source files from args, excluding options and their values
|
||||
fn get_files(args []string) []string {
|
||||
return pref.source_files_from_args(args)
|
||||
}
|
||||
|
||||
fn resolve_own_path() string {
|
||||
arg0 := os.args[0]
|
||||
if os.is_abs_path(arg0) {
|
||||
return arg0
|
||||
}
|
||||
return os.join_path(os.getwd(), arg0)
|
||||
}
|
||||
|
||||
// detect_vroot walks up from `start` looking for a directory containing vlib/builtin.
|
||||
fn detect_vroot(start string) string {
|
||||
mut dir := start
|
||||
if !os.is_abs_path(dir) {
|
||||
dir = os.join_path(os.getwd(), dir)
|
||||
}
|
||||
for _ in 0 .. 10 {
|
||||
if os.is_dir(os.join_path(dir, 'vlib', 'builtin')) {
|
||||
return dir
|
||||
}
|
||||
parent := os.dir(dir)
|
||||
if parent == dir {
|
||||
break
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
fn run_test_self() {
|
||||
t0 := time.now()
|
||||
// Resolve the v2 binary's own path. Cannot use @VEXE because when v1
|
||||
// compiles v2, @VEXE bakes in v1's path instead of v2's.
|
||||
vexe := resolve_own_path()
|
||||
// Walk up from the binary to find the repo root (directory containing vlib/builtin).
|
||||
// This allows running from subdirectories like cmd/v2/.
|
||||
vroot := detect_vroot(vexe)
|
||||
v2_dir := os.join_path(vroot, 'cmd', 'v2')
|
||||
|
||||
mut all_test_files := []string{}
|
||||
|
||||
// Builtin test files
|
||||
for name in ['array_test.v', 'string_test.v', 'map_test.v'] {
|
||||
all_test_files << os.join_path(vroot, 'vlib', 'builtin', name)
|
||||
}
|
||||
|
||||
// Math test
|
||||
all_test_files << os.join_path(vroot, 'vlib', 'math', 'math_test.v')
|
||||
|
||||
// Sumtype tests in cmd/v2/
|
||||
v2_files := os.ls(v2_dir) or { []string{} }
|
||||
for file in v2_files {
|
||||
if file.starts_with('test_sumtype') && file.ends_with('.v') {
|
||||
all_test_files << os.join_path(v2_dir, file)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanc regression tests
|
||||
cleanc_tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
|
||||
cleanc_files := os.ls(cleanc_tests_dir) or { []string{} }
|
||||
for file in cleanc_files {
|
||||
if file.ends_with('.v') && file != 'run_tests.v' {
|
||||
all_test_files << os.join_path(cleanc_tests_dir, file)
|
||||
}
|
||||
}
|
||||
|
||||
total := all_test_files.len
|
||||
mut passed := 0
|
||||
mut failed := 0
|
||||
mut failed_files := []string{}
|
||||
|
||||
eprintln('---- v2 test-self: ${total} test files ----')
|
||||
|
||||
for i, test_file in all_test_files {
|
||||
short_name := test_file.replace(vroot + '/', '')
|
||||
t1 := time.now()
|
||||
|
||||
// Determine output binary path
|
||||
base := os.file_name(test_file).all_before_last('.v')
|
||||
out_bin := os.join_path(os.temp_dir(), 'v2_test_self_${base}')
|
||||
|
||||
// Compile (use os.system to avoid pipe deadlocks with popen/GC)
|
||||
compile_cmd := '${vexe} -o ${out_bin} "${test_file}" > /dev/null 2>&1'
|
||||
compile_ret := os.system(compile_cmd)
|
||||
compile_ms := f64(time.since(t1)) / f64(time.millisecond)
|
||||
|
||||
if compile_ret != 0 || !os.exists(out_bin) {
|
||||
failed++
|
||||
failed_files << short_name
|
||||
eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms ${short_name}')
|
||||
os.rm(out_bin) or {}
|
||||
os.rm('${out_bin}.c') or {}
|
||||
continue
|
||||
}
|
||||
|
||||
// Run the compiled binary
|
||||
t2 := time.now()
|
||||
run_ret := os.system(out_bin + ' > /dev/null 2>&1')
|
||||
run_ms := f64(time.since(t2)) / f64(time.millisecond)
|
||||
|
||||
if run_ret != 0 {
|
||||
failed++
|
||||
failed_files << short_name
|
||||
eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
|
||||
} else {
|
||||
passed++
|
||||
eprintln('OK [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
|
||||
}
|
||||
|
||||
os.rm(out_bin) or {}
|
||||
os.rm('${out_bin}.c') or {}
|
||||
}
|
||||
|
||||
elapsed := time.since(t0)
|
||||
eprintln('------------------------------------------------------------------------')
|
||||
eprintln('${passed} passed, ${failed} failed, ${total} total (${elapsed})')
|
||||
if failed_files.len > 0 {
|
||||
eprintln('Failed:')
|
||||
for f in failed_files {
|
||||
eprintln(' ${f}')
|
||||
}
|
||||
}
|
||||
// Always run the self-compilation chain, even when some tests fail.
|
||||
// Self-compilation chain: v2 -> v3 -> v4 -> v5
|
||||
eprintln('')
|
||||
eprintln('---- v2 self-compilation chain ----')
|
||||
|
||||
v2_source := os.join_path(v2_dir, 'v2.v')
|
||||
backend := 'cleanc'
|
||||
tmpdir := os.temp_dir()
|
||||
v3_bin := os.join_path(tmpdir, 'v2_self_v3')
|
||||
v4_bin := os.join_path(tmpdir, 'v2_self_v4')
|
||||
v5_bin := os.join_path(tmpdir, 'v2_self_v5')
|
||||
|
||||
steps := [
|
||||
[vexe, v3_bin, 'v2 -> v3'],
|
||||
[v3_bin, v4_bin, 'v3 -> v4'],
|
||||
[v4_bin, v5_bin, 'v4 -> v5'],
|
||||
]
|
||||
|
||||
for step in steps {
|
||||
compiler := step[0]
|
||||
out := step[1]
|
||||
label := step[2]
|
||||
ts := time.now()
|
||||
cmd := '${compiler} -gc none -o ${out} -backend ${backend} "${v2_source}" > /dev/null 2>&1'
|
||||
ret := os.system(cmd)
|
||||
ms := f64(time.since(ts)) / f64(time.millisecond)
|
||||
if ret != 0 || !os.exists(out) {
|
||||
eprintln(' FAIL ${label} (${ms:.1} ms)')
|
||||
// Clean up
|
||||
for bin in [v3_bin, v4_bin, v5_bin] {
|
||||
os.rm(bin) or {}
|
||||
os.rm('${bin}.c') or {}
|
||||
}
|
||||
exit(1)
|
||||
}
|
||||
eprintln('OK ${label} (${ms:.1} ms)')
|
||||
}
|
||||
|
||||
// Verify that v3 runs and produces expected output
|
||||
result := os.execute('${v3_bin} 2>&1')
|
||||
expected := 'At least 1 .v file expected'
|
||||
if !result.output.contains(expected) {
|
||||
eprintln("FAIL v3 output check: expected '${expected}', got:")
|
||||
eprintln(result.output)
|
||||
for bin in [v3_bin, v4_bin, v5_bin] {
|
||||
os.rm(bin) or {}
|
||||
os.rm('${bin}.c') or {}
|
||||
}
|
||||
exit(1)
|
||||
}
|
||||
eprintln('OK v3 output verified')
|
||||
|
||||
// Clean up
|
||||
for bin in [v3_bin, v4_bin, v5_bin] {
|
||||
os.rm(bin) or {}
|
||||
os.rm('${bin}.c') or {}
|
||||
}
|
||||
|
||||
total_elapsed := time.since(t0)
|
||||
eprintln('')
|
||||
if failed > 0 {
|
||||
eprintln('=== SELF-COMPILATION OK, but ${failed} test(s) failed ===')
|
||||
eprintln('Total time: ${total_elapsed}')
|
||||
exit(1)
|
||||
}
|
||||
eprintln('=== SELF-COMPILATION TEST PASSED ===')
|
||||
eprintln('Total time: ${total_elapsed}')
|
||||
}
|
||||
Reference in new issue
Block a user