added --no-artifact and --script and a small fix to the syscall word
This commit is contained in:
2
SPEC.md
2
SPEC.md
@@ -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.
|
||||
|
||||
## 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.
|
||||
- **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`.
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
hello world
|
||||
[info] built <build>/ct_test
|
||||
[warn] redefining word syscall
|
||||
|
||||
97
main.py
97
main.py
@@ -4312,7 +4312,9 @@ def _compile_syscall_stub(vm: CompileTimeVM) -> Any:
|
||||
# Stack protocol (matching _emit_syscall_intrinsic):
|
||||
# TOS: syscall number → rax
|
||||
# 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 = [
|
||||
"_stub_entry:",
|
||||
" push rbx",
|
||||
@@ -4339,43 +4341,57 @@ def _compile_syscall_stub(vm: CompileTimeVM) -> Any:
|
||||
" jle _count_clamped",
|
||||
" mov rcx, 6",
|
||||
"_count_clamped:",
|
||||
# Save arg count in r14 and syscall num in r15
|
||||
" mov r14, rcx",
|
||||
# Save syscall num in r15
|
||||
" 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)
|
||||
" cmp r15, 60",
|
||||
" je _do_exit",
|
||||
" cmp r15, 231",
|
||||
" je _do_exit",
|
||||
# Assign args to syscall registers from the scratch area
|
||||
# arg0 → rdi, arg1 → rsi, arg2 → rdx, arg3 → r10, arg4 → r8, arg5 → r9
|
||||
" mov rdi, [rsp]",
|
||||
" mov rsi, [rsp+8]",
|
||||
" mov rdx, [rsp+16]",
|
||||
" mov r10, [rsp+24]",
|
||||
" mov r8, [rsp+32]",
|
||||
" mov r9, [rsp+40]",
|
||||
# Clear syscall arg registers
|
||||
" xor rdi, rdi",
|
||||
" xor rsi, rsi",
|
||||
" xor rdx, rdx",
|
||||
" xor r10, r10",
|
||||
" xor r8, r8",
|
||||
" xor r9, r9",
|
||||
# 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
|
||||
" syscall",
|
||||
# Push result
|
||||
" sub r12, 8",
|
||||
" mov [r12], rax",
|
||||
# Normal return: flag=0
|
||||
" add rsp, 48",
|
||||
" mov rax, [rsp]", # output-struct pointer
|
||||
" mov qword [rax], r12",
|
||||
" mov qword [rax+8], r13",
|
||||
@@ -4384,8 +4400,12 @@ def _compile_syscall_stub(vm: CompileTimeVM) -> Any:
|
||||
" jmp _stub_epilogue",
|
||||
# Exit path: don't actually call syscall, just report it
|
||||
"_do_exit:",
|
||||
" mov rbx, [rsp]", # arg0 = exit code
|
||||
" add rsp, 48",
|
||||
" xor rbx, rbx",
|
||||
" 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 qword [rax], r12",
|
||||
" mov qword [rax+8], r13",
|
||||
@@ -4711,8 +4731,8 @@ class Compiler:
|
||||
word = self.dictionary.lookup("syscall")
|
||||
if word is None:
|
||||
word = Word(name="syscall")
|
||||
self.dictionary.register(word)
|
||||
word.intrinsic = self._emit_syscall_intrinsic
|
||||
self.dictionary.register(word)
|
||||
|
||||
def _emit_syscall_intrinsic(self, builder: FunctionEmitter) -> None:
|
||||
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("--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("--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
|
||||
args, unknown = parser.parse_known_args(argv)
|
||||
@@ -5298,6 +5324,10 @@ def cli(argv: Sequence[str]) -> int:
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if args.script:
|
||||
args.no_artifact = True
|
||||
args.ct_run_main = True
|
||||
|
||||
artifact_kind = args.artifact
|
||||
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):
|
||||
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:
|
||||
try:
|
||||
if args.temp_dir.exists():
|
||||
@@ -5322,7 +5355,7 @@ def cli(argv: Sequence[str]) -> int:
|
||||
if args.source is None and not args.repl:
|
||||
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
|
||||
default_outputs = {
|
||||
"exe": Path("a.out"),
|
||||
@@ -5366,6 +5399,10 @@ def cli(argv: Sequence[str]) -> int:
|
||||
print(f"[info] wrote {asm_path}")
|
||||
return 0
|
||||
|
||||
if args.no_artifact:
|
||||
print("[info] skipped artifact generation (--no-artifact)")
|
||||
return 0
|
||||
|
||||
run_nasm(asm_path, obj_path, debug=args.debug)
|
||||
if args.output.parent and not args.output.parent.exists():
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user