Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use_nix
28 changes: 27 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,33 @@ class Config:
"stdout" : "File to output stdout of complier and program",
"input" : "Stdin for program",
"error" : "Stderr for program",
"link_with_libc" : "Statically link with libc",
# "library_path" : "add library path 'dir'",
# "link_with" : "link with dynamic or static library 'lib'"
}

BOOL_OPTIONS: Dict[str, Tuple[List[str], bool]] = {
"run" : (["-r", "-run"], False),
"dump" : (["-d", "-dump"], False),
"dump_tokens" : (["-dt", "-dump_tokens"], False),
"dump_tc" : (["-dtc", "-dump_tc"], False),
"link_with_libc": (["-lc", "--libc"], False),
}

REGULAR_OPTIONS: Dict[str, List[str]] = {
REGULAR_OPTIONS: Dict[str, Tuple[List[str], bool]] = {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type annotation is wrong in master, but the new annotation is wrong as well, it's clearly Dict[str, Tuple[List[str], str]
TODO: make this a proper dataclass, maybe?

"out" : (["-o", "--out"], None),
"target" : (["-t", "--target"], "fasm_x86_64_linux"),
"dump_proc" : (["-dp", "--dump_proc"], None),
"stdout" : (["-stdo", "--stdout"], None),
"input" : (["-i", "--input"], None),
"error" : (["-e", "--error"], None),
}

ARRAY_OPTIONS: Dict[str, Tuple[List[str], bool]] = {
# "library_path" : ((["-L", "--library-path"]), None),
# "link_with" : ((["-l", "--link-with"]), None),
}

CONFIG_REGULAR_OPTIONS: List[str] = ["out", "target"]

CONFIG_BOOL_OPTIONS: Dict[str, bool] = {
Expand Down Expand Up @@ -102,6 +112,11 @@ def setup_args_parser(self) -> argparse.ArgumentParser:
*args[0], default=None, dest=name, help=self.DESCRIPTIONS[name]
)

for name, args in self.ARRAY_OPTIONS.items():
args_parser.add_argument(
*args[0], default=None, dest=name, help=self.DESCRIPTIONS[name], action='append'
)

return args_parser

def _validate_target(self):
Expand Down Expand Up @@ -152,6 +167,17 @@ def define_properties(self):
)
)

for name in self.ARRAY_OPTIONS:
setattr(
self.__class__, name,
property(
fget=lambda self, name=name: self.config.get(
name, getattr(self.args, name)
if getattr(self.args, name) is not None
else self.REGULAR_OPTIONS[name][1])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be else self.ARRAY_OPTIONS[name][1]

)
)

for name, default in {**self.CONFIG_BOOL_OPTIONS, **self.CONFIG_INT_OPTIONS}.items():
setattr(
self.__class__, name,
Expand Down
1 change: 1 addition & 0 deletions cont.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def main(lsp_mode: bool = False):
if the function is used in code and not by calling the script from command line.
"""
config = Config(sys.argv, lsp_mode=lsp_mode)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a change here?

State.config = config

file_name = os.path.splitext(config.program)[0]
Expand Down
36 changes: 32 additions & 4 deletions generating/fasm_x86_64_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ def compile_ops_fasm_x86_64_linux(ops: List[Op]):
f.write(generate_fasm_x86_64_linux(ops))

subprocess.run(["fasm", f"{out}.asm"], stdin=sys.stdin, stderr=sys.stderr)

linker_args = ["ld", f"{out}.o", "-o", f"{out}"]
if State.config.link_with_libc:
linker_args += ["-lc", "--static"]
subprocess.run(linker_args, stdin=sys.stdin, stderr=sys.stderr)
os.chmod(
out, os.stat(out).st_mode | stat.S_IEXEC
) # Give execution permission to the file
Expand Down Expand Up @@ -113,9 +118,10 @@ def compile_ops_fasm_x86_64_linux(ops: List[Op]):
def generate_fasm_x86_64_linux(ops: List[Op]) -> str:
"""Generates a string of fasm assembly for the program from the list of operations `ops`."""
buf = (
"format ELF64 executable 3\n"
"segment readable executable\n"
"entry _start\n"
"format ELF64\n"
f"{generate_imports()}"
"section '.text' executable\n"
"public _start\n"
f"{INDEX_ERROR_CODE if State.config.re_IOR else ''}"
f"{NULL_POINTER_CODE if State.config.re_NPD else ''}"
"_start:\n"
Expand All @@ -135,7 +141,7 @@ def generate_fasm_x86_64_linux(ops: List[Op]) -> str:
"mov rax, 60\n"
"xor rdi, rdi\n"
"syscall\n"
"segment readable writeable\n"
"section '.data' writeable\n"
f"{ior_code if State.config.re_IOR else ''}\n"
f"{npd_code if State.config.re_NPD else ''}\n"
f"{generate_fasm_types()}\n"
Expand All @@ -160,6 +166,15 @@ def generate_fasm_x86_64_linux(ops: List[Op]) -> str:
return buf


def generate_imports() -> str:
buf = ""
for (proc_name, _) in State.imported_procs:
buf += (
f"extrn {proc_name}\n"
)

return buf

def generate_fasm_types() -> str:
"""
Generates a string of assembly, which if put into the .data segment,
Expand Down Expand Up @@ -419,6 +434,19 @@ def generate_op_fasm_x86_64_linux(op: Op) -> str:
"push rax\n"
)
elif op.type == OpType.CALL:
buf = ""

if op.operand.is_imported:
calling_convention = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a constant declared globally

in_stack = op.operand.in_stack
while len(in_stack) != 0 and len(calling_convention) != 0:
buf += f"pop {calling_convention[0]}\n"
calling_convention.pop(0)
in_stack.pop(0)

buf += f"call {op.operand.name}\npush rax\n"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't push rax if the function does not return a value

return comment + buf

return comment + f"call addr_{op.operand.ip}\n"
elif op.type == OpType.TYPED_LOAD:
cont_assert(not isinstance(op.operand, Struct), "Bug in parsing of structure types")
Expand Down
10 changes: 7 additions & 3 deletions parsing/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,10 +991,14 @@ def parse_token(token: str, ops: List[Op]) -> Union[Op, List[Op]]:
return Op(OpType.CALL_ADDR, None)

elif token == "#import":
assert State.config.target == "wat64", "Current target does not support imports"

assert State.config.target == "wat64" or State.config.target == "fasm_x86_64_linux", "Current target does not support imports"

path = ""
name, name_loc = safe_next_token("Expected a function name")
path, _ = safe_next_token("Expected a path")

if State.config.target == "wat64":
path, _ = safe_next_token("Expected a path")

State.check_name((name, name_loc))
if ";" not in name:
in_types, out_types, _ = parse_signature((name, name_loc), {}, ";")
Expand Down
13 changes: 13 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages; [
gcc14
python312Full
nodejs_23
wabt
fasm-bin
musl
python312Packages.pytest
];
}
3 changes: 3 additions & 0 deletions std/core.cn
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
include platform.cn
include stack.cn
#if platform Platform.fasm_x86_64_linux ==;
include ffi.cn
#endif

proc / int int -> int: div drop end
proc % int int -> int: div swap drop end
Expand Down
2 changes: 2 additions & 0 deletions std/ffi.cn
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
typealias c_int int
typealias c_void void
1 change: 1 addition & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function ContExitException(message) {
error.name = "ContExitException";
return error;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a change here?

ContExitException.prototype = Object.create(Error.prototype);

function bnToBuf(bn) {
Expand Down
5 changes: 4 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test(test_name):

subprocess.run(
[
"python", "cont.py", f"tests/temp/code_{test_name}.cn",
"python", "cont.py", "-lc", f"tests/temp/code_{test_name}.cn",
"-t", "fasm_x86_64_linux",
"-i", f"tests/temp/stdin_{test_name}",
"-e", f"tests/results/{test_name}_stderr",
Expand Down Expand Up @@ -68,6 +68,9 @@ def test(test_name):
else:
@pytest.mark.parametrize("test_name", tests)
def test_node_wat64(test_name):
if test_name == "extern":
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be better if this was a part of the test file itself, but this is a matter for a different PR for sure

return

with open(f"tests/{test_name}", "r") as f:
test = f.read()

Expand Down
7 changes: 7 additions & 0 deletions tests/extern
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import puts ptr -> int;
#import exit int;

"Hello, World!\0" puts drop
0 exit
:
Hello, World!
16 changes: 16 additions & 0 deletions type_checking/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ def __hash__(self) -> int:
return hash(self.text_repr())


class Void(Type):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the void type, because we have native untyped pointers in cont

"""
Void type. Provided for C FFI Purposes.
"""
def __eq__(self, other) -> bool:
return true

def text_repr(self) -> str:
return f"void"

def __hash__(self) -> int:
return hash(self.text_repr())


def type_to_str(_type: Type) -> str:
"""
Converts cont type object to a human-readable string
Expand Down Expand Up @@ -260,6 +274,8 @@ def parse_type(
result = Int()
elif name == "ptr":
result = Ptr()
elif name == "void":
result = Void()
elif name == "addr":
assert end is None or end not in og_name, "Expected procedure name"
try:
Expand Down
Loading