added --no-artifact and --script and a small fix to the syscall word

This commit is contained in:
igor
2026-02-18 08:48:30 +01:00
parent e95244c7f3
commit 9732f0e74a
3 changed files with 68 additions and 32 deletions

View File

@@ -9,7 +9,7 @@ This document reflects the implementation that ships in this repository today (`
- **Unsafe by design** Memory, syscalls, inline assembly, and FFI expose raw machine power. The standard library is intentionally thin and policy-free. - **Unsafe by design** Memory, syscalls, inline assembly, and FFI expose raw machine power. The standard library is intentionally thin and policy-free.
## 2. Toolchain and Repository Layout ## 2. Toolchain and Repository Layout
- **Driver (`main.py`)** Supports `python main.py source.sl -o a.out`, `--emit-asm`, `--run`, `--dbg`, `--repl`, `--temp-dir`, `--clean`, repeated `-I/--include` paths, and repeated `-l` linker flags (either `-lfoo` or `-l libc.so.6`). Unknown `-l` flags are collected and forwarded to the linker. Pass `--ct-run-main` to run the program's `main` word on the compile-time VM before NASM/ld run, which surfaces discrepancies between compile-time and runtime semantics. - **Driver (`main.py`)** Supports `python main.py source.sl -o a.out`, `--emit-asm`, `--run`, `--dbg`, `--repl`, `--temp-dir`, `--clean`, repeated `-I/--include` paths, and repeated `-l` linker flags (either `-lfoo` or `-l libc.so.6`). Unknown `-l` flags are collected and forwarded to the linker. Pass `--ct-run-main` to run the program's `main` word on the compile-time VM before NASM/ld run, which surfaces discrepancies between compile-time and runtime semantics. Pass `--no-artifact` to stop after compilation/assembly emission without building an output file, or use `--script` as shorthand for `--no-artifact --ct-run-main`.
- **REPL** `--repl` launches a stateful session with commands such as `:help`, `:reset`, `:load`, `:call <word>`, `:edit`, and `:show`. The REPL still emits/links entire programs for each run; it simply manages the session source for you. - **REPL** `--repl` launches a stateful session with commands such as `:help`, `:reset`, `:load`, `:call <word>`, `:edit`, and `:show`. The REPL still emits/links entire programs for each run; it simply manages the session source for you.
- **Imports** `import relative/or/absolute/path.sl` inserts the referenced file textually. Resolution order: (1) absolute path, (2) relative to the importing file, (3) each include path (defaults: project root and `./stdlib`). Each file is included at most once per compilation unit. Import lines leave blank placeholders so error spans stay meaningful. - **Imports** `import relative/or/absolute/path.sl` inserts the referenced file textually. Resolution order: (1) absolute path, (2) relative to the importing file, (3) each include path (defaults: project root and `./stdlib`). Each file is included at most once per compilation unit. Import lines leave blank placeholders so error spans stay meaningful.
- **Workspace** `stdlib/` holds library modules, `tests/` contains executable samples with `.expected` outputs, `extra_tests/` houses standalone integration demos, and `libs/` collects opt-in extensions such as `libs/fn.sl` and `libs/nob.sl`. - **Workspace** `stdlib/` holds library modules, `tests/` contains executable samples with `.expected` outputs, `extra_tests/` houses standalone integration demos, and `libs/` collects opt-in extensions such as `libs/fn.sl` and `libs/nob.sl`.

View File

@@ -1,3 +1,2 @@
hello world hello world
[info] built <build>/ct_test [info] built <build>/ct_test
[warn] redefining word syscall

97
main.py
View File

@@ -4312,7 +4312,9 @@ def _compile_syscall_stub(vm: CompileTimeVM) -> Any:
# Stack protocol (matching _emit_syscall_intrinsic): # Stack protocol (matching _emit_syscall_intrinsic):
# TOS: syscall number → rax # TOS: syscall number → rax
# TOS-1: arg count → rcx # TOS-1: arg count → rcx
# then up to 6 args → rdi, rsi, rdx, r10, r8, r9 # then args on stack as ... arg0 arg1 ... argN (argN is top)
#
lines = [ lines = [
"_stub_entry:", "_stub_entry:",
" push rbx", " push rbx",
@@ -4339,43 +4341,57 @@ def _compile_syscall_stub(vm: CompileTimeVM) -> Any:
" jle _count_clamped", " jle _count_clamped",
" mov rcx, 6", " mov rcx, 6",
"_count_clamped:", "_count_clamped:",
# Save arg count in r14 and syscall num in r15 # Save syscall num in r15
" mov r14, rcx",
" mov r15, rax", " mov r15, rax",
# Pop args into scratch area on machine stack (up to 6 qwords)
# We pop them into rbx, r8-r11 area, then assign to syscall regs after
# Pop all args onto machine stack (reverse order)
" sub rsp, 48", # 6 * 8 bytes for args
" xor rbx, rbx", # index
"_pop_args:",
" cmp rbx, r14",
" jge _pop_done",
" mov rax, [r12]",
" add r12, 8",
" mov [rsp + rbx*8], rax",
" inc rbx",
" jmp _pop_args",
"_pop_done:",
# Check for exit (60) / exit_group (231) # Check for exit (60) / exit_group (231)
" cmp r15, 60", " cmp r15, 60",
" je _do_exit", " je _do_exit",
" cmp r15, 231", " cmp r15, 231",
" je _do_exit", " je _do_exit",
# Assign args to syscall registers from the scratch area # Clear syscall arg registers
# arg0 → rdi, arg1 → rsi, arg2 → rdx, arg3 → r10, arg4 → r8, arg5 → r9 " xor rdi, rdi",
" mov rdi, [rsp]", " xor rsi, rsi",
" mov rsi, [rsp+8]", " xor rdx, rdx",
" mov rdx, [rsp+16]", " xor r10, r10",
" mov r10, [rsp+24]", " xor r8, r8",
" mov r8, [rsp+32]", " xor r9, r9",
" mov r9, [rsp+40]", # Pop args in the same order as _emit_syscall_intrinsic
" cmp rcx, 6",
" jl _skip_r9",
" mov r9, [r12]",
" add r12, 8",
"_skip_r9:",
" cmp rcx, 5",
" jl _skip_r8",
" mov r8, [r12]",
" add r12, 8",
"_skip_r8:",
" cmp rcx, 4",
" jl _skip_r10",
" mov r10, [r12]",
" add r12, 8",
"_skip_r10:",
" cmp rcx, 3",
" jl _skip_rdx",
" mov rdx, [r12]",
" add r12, 8",
"_skip_rdx:",
" cmp rcx, 2",
" jl _skip_rsi",
" mov rsi, [r12]",
" add r12, 8",
"_skip_rsi:",
" cmp rcx, 1",
" jl _skip_rdi",
" mov rdi, [r12]",
" add r12, 8",
"_skip_rdi:",
" mov rax, r15", # syscall number " mov rax, r15", # syscall number
" syscall", " syscall",
# Push result # Push result
" sub r12, 8", " sub r12, 8",
" mov [r12], rax", " mov [r12], rax",
# Normal return: flag=0 # Normal return: flag=0
" add rsp, 48",
" mov rax, [rsp]", # output-struct pointer " mov rax, [rsp]", # output-struct pointer
" mov qword [rax], r12", " mov qword [rax], r12",
" mov qword [rax+8], r13", " mov qword [rax+8], r13",
@@ -4384,8 +4400,12 @@ def _compile_syscall_stub(vm: CompileTimeVM) -> Any:
" jmp _stub_epilogue", " jmp _stub_epilogue",
# Exit path: don't actually call syscall, just report it # Exit path: don't actually call syscall, just report it
"_do_exit:", "_do_exit:",
" mov rbx, [rsp]", # arg0 = exit code " xor rbx, rbx",
" add rsp, 48", " cmp rcx, 1",
" jl _exit_code_ready",
" mov rbx, [r12]", # arg0 = exit code (for exit/exit_group)
" add r12, 8",
"_exit_code_ready:",
" mov rax, [rsp]", # output-struct pointer " mov rax, [rsp]", # output-struct pointer
" mov qword [rax], r12", " mov qword [rax], r12",
" mov qword [rax+8], r13", " mov qword [rax+8], r13",
@@ -4711,8 +4731,8 @@ class Compiler:
word = self.dictionary.lookup("syscall") word = self.dictionary.lookup("syscall")
if word is None: if word is None:
word = Word(name="syscall") word = Word(name="syscall")
self.dictionary.register(word)
word.intrinsic = self._emit_syscall_intrinsic word.intrinsic = self._emit_syscall_intrinsic
self.dictionary.register(word)
def _emit_syscall_intrinsic(self, builder: FunctionEmitter) -> None: def _emit_syscall_intrinsic(self, builder: FunctionEmitter) -> None:
label_id = self._syscall_label_counter label_id = self._syscall_label_counter
@@ -5283,6 +5303,12 @@ def cli(argv: Sequence[str]) -> int:
parser.add_argument("-l", dest="libs", action="append", default=[], help="pass library to linker (e.g. -l m or -l libc.so.6)") parser.add_argument("-l", dest="libs", action="append", default=[], help="pass library to linker (e.g. -l m or -l libc.so.6)")
parser.add_argument("--no-folding", action="store_true", help="disable constant folding optimization") parser.add_argument("--no-folding", action="store_true", help="disable constant folding optimization")
parser.add_argument("--ct-run-main", action="store_true", help="execute 'main' via the compile-time VM after parsing") parser.add_argument("--ct-run-main", action="store_true", help="execute 'main' via the compile-time VM after parsing")
parser.add_argument("--no-artifact", action="store_true", help="compile source but skip producing final output artifact")
parser.add_argument(
"--script",
action="store_true",
help="shortcut for --no-artifact --ct-run-main",
)
# Parse known and unknown args to allow -l flags anywhere # Parse known and unknown args to allow -l flags anywhere
args, unknown = parser.parse_known_args(argv) args, unknown = parser.parse_known_args(argv)
@@ -5298,6 +5324,10 @@ def cli(argv: Sequence[str]) -> int:
else: else:
i += 1 i += 1
if args.script:
args.no_artifact = True
args.ct_run_main = True
artifact_kind = args.artifact artifact_kind = args.artifact
folding_enabled = not args.no_folding folding_enabled = not args.no_folding
@@ -5307,6 +5337,9 @@ def cli(argv: Sequence[str]) -> int:
if artifact_kind != "exe" and (args.run or args.dbg): if artifact_kind != "exe" and (args.run or args.dbg):
parser.error("--run/--dbg are only available when --artifact exe is selected") parser.error("--run/--dbg are only available when --artifact exe is selected")
if args.no_artifact and (args.run or args.dbg):
parser.error("--run/--dbg are not available with --no-artifact")
if args.clean: if args.clean:
try: try:
if args.temp_dir.exists(): if args.temp_dir.exists():
@@ -5322,7 +5355,7 @@ def cli(argv: Sequence[str]) -> int:
if args.source is None and not args.repl: if args.source is None and not args.repl:
parser.error("the following arguments are required: source") parser.error("the following arguments are required: source")
if not args.repl and args.output is None: if not args.repl and args.output is None and not args.no_artifact:
stem = args.source.stem stem = args.source.stem
default_outputs = { default_outputs = {
"exe": Path("a.out"), "exe": Path("a.out"),
@@ -5366,6 +5399,10 @@ def cli(argv: Sequence[str]) -> int:
print(f"[info] wrote {asm_path}") print(f"[info] wrote {asm_path}")
return 0 return 0
if args.no_artifact:
print("[info] skipped artifact generation (--no-artifact)")
return 0
run_nasm(asm_path, obj_path, debug=args.debug) run_nasm(asm_path, obj_path, debug=args.debug)
if args.output.parent and not args.output.parent.exists(): if args.output.parent and not args.output.parent.exists():
args.output.parent.mkdir(parents=True, exist_ok=True) args.output.parent.mkdir(parents=True, exist_ok=True)