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.
|
- **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`.
|
||||||
|
|||||||
@@ -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
97
main.py
@@ -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")
|
||||||
word.intrinsic = self._emit_syscall_intrinsic
|
|
||||||
self.dictionary.register(word)
|
self.dictionary.register(word)
|
||||||
|
word.intrinsic = self._emit_syscall_intrinsic
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user