Skip to content

Commit 534c7bf

Browse files
committed
fix(sdk): c64 linking true object
1 parent 147fe58 commit 534c7bf

5 files changed

Lines changed: 91 additions & 42 deletions

File tree

build.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,12 +1198,17 @@ fn addC64Exe(
11981198
});
11991199
exe.bundle_compiler_rt = false;
12001200
exe.lto = .full;
1201+
exe.forceUndefinedSymbol("__zig_call_main_section");
1202+
exe.forceUndefinedSymbol("main");
12011203
// basic-header.S and unmap-basic.S replace INPUT(basic-header.o) / INPUT(unmap-basic.o).
12021204
exe.root_module.addAssemblyFile(sdk_dep.path("mos-platform/c64/basic-header.S"));
12031205
exe.root_module.addAssemblyFile(sdk_dep.path("mos-platform/c64/unmap-basic.S"));
1206+
if (libs.crt0_obj) |obj| exe.root_module.addObject(obj);
12041207
exe.root_module.linkLibrary(libs.crt);
12051208
exe.root_module.linkLibrary(libs.crt0);
12061209
exe.root_module.linkLibrary(libs.c);
1210+
if (libs.printf) |libprintf| exe.root_module.linkLibrary(libprintf);
1211+
if (libs.mem) |mem_obj| exe.root_module.addObject(mem_obj);
12071212
if (with_printf_flt) exe.root_module.linkSystemLibrary("printf_flt", .{ .use_pkg_config = .no });
12081213
exe.setLibCFile(libc_txt);
12091214
exe.root_module.link_libc = true;

c64/fibonacci/fibonacci.c

Lines changed: 0 additions & 30 deletions
This file was deleted.

c64/fibonacci/fibonacci.zig

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
// Copyright (c) 2024 Matheus C. França
22
// SPDX-License-Identifier: Apache-2.0
3-
//! C64 Fibonacci — computes and prints fib(0..9) using comptime recursion.
3+
//! C64 Fibonacci — prints fib(0..9) from a ROM lookup table.
44
pub const panic = @import("mos_panic");
55
const std = @import("std");
66

7-
/// Recursive Fibonacci evaluated at comptime when called with a comptime argument.
8-
fn fibonacci(comptime T: type, n: T) T {
9-
return if (n < 2) n else fibonacci(T, n - 1) + fibonacci(T, n - 2);
7+
fn fibonacci(comptime n: usize) c_int {
8+
return if (n <= 1) @intCast(n) else fibonacci(n - 1) + fibonacci(n - 2);
109
}
1110

11+
const fib_table = blk: {
12+
var table: [10]c_int = undefined;
13+
for (0..10) |i| table[i] = fibonacci(i);
14+
break :blk table;
15+
};
16+
1217
export fn main() void {
13-
comptime var i: u8 = 0;
14-
inline while (i < 10) : (i += 1) {
15-
_ = std.c.printf("fib(%d) = %d\n", @as(c_int, i), @as(c_int, fibonacci(u8, i)));
18+
// Split into two single-vararg printf calls: LLVM-MOS misplaces the second
19+
// c_int vararg (slot 4 instead of slot 2) when the loop is unrolled with
20+
// two varargs, producing 0 for every fib value.
21+
var i: c_int = 0;
22+
const ip: *volatile c_int = &i;
23+
while (ip.* < 10) : (ip.* += 1) {
24+
const idx: usize = @intCast(ip.*);
25+
_ = std.c.printf("fib(%d) = ", ip.*);
26+
_ = std.c.printf("%d\n", fib_table[idx]);
1627
}
1728
}

c64/hello/hello.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
//! Uses the translated c64.h module for VIC struct type and color constants.
55
pub const panic = @import("mos_panic");
66

7+
const std = @import("std");
78
const c64 = @import("c64");
89

9-
const VIC: *volatile c64.struct___vic2 = @ptrFromInt(0xD000);
10+
const VIC: *volatile c64.__vic2 = @ptrFromInt(0xD000);
1011

1112
export fn main() void {
1213
VIC.unnamed_2.unnamed_0.bgcolor0 = c64.COLOR_BLACK;
14+
_ = std.c.printf("Hello Zig!\n");
1315
while (true) {
1416
VIC.bordercolor +%= 1;
1517
}

sdk/build.zig

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ pub const Libs = struct {
2626
// Platforms that need a strong __memset (overrides the weak recursive stub):
2727
// built as a TRUE object so ld.lld sees the strong definition before the archive.
2828
mem: ?*std.Build.Step.Compile = null,
29+
// C64/CBM: printf.cc + varint.cc compiled with lto=.none (C++ bitcode crashes LLVM LTO codegen for 6502).
30+
printf: ?*std.Build.Step.Compile = null,
31+
// C64/CBM: crt0.S must be a TRUE object (not an archive member).
32+
// common-crt0-o in CMake = add_platform_object_file → always force-linked.
33+
// Without this, .call_main (jsr main) is a section-only archive member with no
34+
// exported symbol, so ld.lld never extracts it → main is never called.
35+
crt0_obj: ?*std.Build.Step.Compile = null,
2936
};
3037

3138
/// Build platform libraries for `pd` from the SDK source tree at `sdk_root`.
@@ -138,16 +145,28 @@ pub fn buildPlatform(b: *std.Build, sdk_root: []const u8, pd: Platform, opt: std
138145
libcrt0.root_module.addIncludePath(.{ .cwd_relative = com_inc });
139146
libcrt0.root_module.addCSourceFiles(.{
140147
.root = .{ .cwd_relative = crt0_dir },
141-
.files = &.{ "crt0.S", "init-stack.S" },
148+
// crt0.S is kept here for sim (sim uses Zig start.zig + forceUndefinedSymbol
149+
// for startup, so the archive copy is harmless). For CBM platforms, crt0.S
150+
// is built as a TRUE object (crt0_obj) and added directly to each exe, because
151+
// its .call_main section has no exported symbol → ld.lld would silently skip
152+
// the archive member → main is never called.
153+
.files = if (std.mem.eql(u8, pd.name, "sim")) &.{ "crt0.S", "init-stack.S" } else &.{"init-stack.S"},
142154
});
143155
libcrt0.root_module.addCSourceFiles(.{
144156
.root = .{ .cwd_relative = crt0_dir },
145157
.files = &.{"copy-zp-data.c"},
146158
});
147-
const exit_file: []const u8 = if (std.mem.eql(u8, pd.name, "sim")) "exit-custom.S" else "exit-loop.c";
159+
// CBM exit: exit-custom.S defines __after_main (jmp exit) so crt0.S's undefined
160+
// __after_main reference pulls it from the archive. exit.c provides exit() which
161+
// calls _fini() + _Exit(). exit-loop.c provides _Exit() = infinite loop.
162+
// sim only needs exit-custom.S (it defines __after_main for that platform too).
163+
const exit_files: []const []const u8 = if (std.mem.eql(u8, pd.name, "sim"))
164+
&.{"exit-custom.S"}
165+
else
166+
&.{ "exit-custom.S", "exit-loop.c", "exit.c" };
148167
libcrt0.root_module.addCSourceFiles(.{
149168
.root = .{ .cwd_relative = b.fmt("{s}/exit", .{crt0_dir}) },
150-
.files = &.{exit_file},
169+
.files = exit_files,
151170
});
152171

153172
// libc — platform I/O and kernal wrappers.
@@ -160,12 +179,14 @@ pub fn buildPlatform(b: *std.Build, sdk_root: []const u8, pd: Platform, opt: std
160179
.files = &.{ "putchar.c", "stdlib.c", "sim-io.c" },
161180
});
162181
} else {
182+
const com_c_dir = b.fmt("{s}/c", .{common});
163183
const asm_files: []const []const u8 = if (std.mem.eql(u8, pd.name, "mega65"))
164184
&.{ "filevars.s", "kernal.S" }
165185
else
166186
&.{ "basic-header.S", "kernal.S", "unmap-basic.S", "devnum.s" };
167187
libc.root_module.addIncludePath(.{ .cwd_relative = plat_dir });
168188
libc.root_module.addIncludePath(.{ .cwd_relative = comm_dir });
189+
libc.root_module.addIncludePath(.{ .cwd_relative = com_c_dir });
169190
libc.root_module.addIncludePath(.{ .cwd_relative = com_asm });
170191
libc.root_module.addIncludePath(.{ .cwd_relative = com_inc });
171192
libc.root_module.addCSourceFiles(.{
@@ -174,8 +195,48 @@ pub fn buildPlatform(b: *std.Build, sdk_root: []const u8, pd: Platform, opt: std
174195
});
175196
libc.root_module.addCSourceFiles(.{
176197
.root = .{ .cwd_relative = comm_dir },
177-
.files = &.{ "abort.c", "cbm_k_bsout.c", "cbm_k_chrout.c", "chrout.c", "char-conv.c" },
198+
.files = &.{ "abort.c", "cbm_k_bsout.c", "cbm_k_chrout.c", "chrout.c", "char-conv.c", "putchar.c", "getchar.c" },
199+
});
200+
libc.root_module.addCSourceFiles(.{
201+
.root = .{ .cwd_relative = com_c_dir },
202+
.files = &.{ "stdio-minimal.c", "mem.c", "util.c", "string.c" },
203+
});
204+
// Build printf.cc + varint.cc with lto=.none: C++ bitcode crashes LLVM LTO
205+
// codegen for the 6502 target. Pre-compiling to native code avoids the crash.
206+
const libprintf = addLib(b, "printf", target, opt);
207+
libprintf.lto = .none;
208+
libprintf.root_module.addIncludePath(.{ .cwd_relative = plat_dir });
209+
libprintf.root_module.addIncludePath(.{ .cwd_relative = comm_dir });
210+
libprintf.root_module.addIncludePath(.{ .cwd_relative = com_c_dir });
211+
libprintf.root_module.addIncludePath(.{ .cwd_relative = com_asm });
212+
libprintf.root_module.addIncludePath(.{ .cwd_relative = com_inc });
213+
libprintf.root_module.addCSourceFiles(.{
214+
.root = .{ .cwd_relative = com_c_dir },
215+
.files = &.{ "printf.cc", "varint.cc" },
216+
.flags = &.{ "-fno-exceptions", "-fno-rtti" },
217+
});
218+
// sdk/mem.s — strong __memset + abort TRUE object (same pattern as NES/SNES).
219+
const mem_obj = b.addObject(.{
220+
.name = "mem",
221+
.root_module = b.createModule(.{ .target = target, .optimize = opt }),
222+
});
223+
mem_obj.root_module.addCSourceFiles(.{ .root = b.path("sdk"), .files = &.{"mem.s"} });
224+
mem_obj.lto = .none;
225+
// crt0.S TRUE object — mirrors cmake add_platform_object_file(common-crt0-o).
226+
// Must be linked directly (not via archive) so its .call_main / .fini_rts
227+
// anonymous sections reach the linker. lto=.none matches libcrt0 policy.
228+
const crt0_obj = b.addObject(.{
229+
.name = "crt0",
230+
.root_module = b.createModule(.{ .target = target, .optimize = opt }),
231+
});
232+
crt0_obj.root_module.addIncludePath(.{ .cwd_relative = com_asm });
233+
crt0_obj.root_module.addIncludePath(.{ .cwd_relative = com_inc });
234+
crt0_obj.root_module.addCSourceFiles(.{
235+
.root = .{ .cwd_relative = crt0_dir },
236+
.files = &.{"crt0.S"},
178237
});
238+
crt0_obj.lto = .none;
239+
return .{ .crt = libcrt, .crt0 = libcrt0, .c = libc, .printf = libprintf, .mem = mem_obj, .crt0_obj = crt0_obj };
179240
}
180241

181242
return .{ .crt = libcrt, .crt0 = libcrt0, .c = libc };

0 commit comments

Comments
 (0)