-
Notifications
You must be signed in to change notification settings - Fork 3
Initial implementation of FFI/#import for fasm
#52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| use_nix |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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]] = { | ||
| "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] = { | ||
|
|
@@ -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): | ||
|
|
@@ -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]) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
| ) | ||
| ) | ||
|
|
||
| for name, default in {**self.CONFIG_BOOL_OPTIONS, **self.CONFIG_INT_OPTIONS}.items(): | ||
| setattr( | ||
| self.__class__, name, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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" | ||
|
|
@@ -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" | ||
|
|
@@ -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, | ||
|
|
@@ -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"] | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You shouldn't |
||
| 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") | ||
|
|
||
| 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 | ||
| ]; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| typealias c_int int | ||
| typealias c_void void |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ function ContExitException(message) { | |
| error.name = "ContExitException"; | ||
| return error; | ||
| } | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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", | ||
|
|
@@ -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": | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
|
|
||
|
|
||
| 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! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,6 +184,20 @@ def __hash__(self) -> int: | |
| return hash(self.text_repr()) | ||
|
|
||
|
|
||
| class Void(Type): | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -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: | ||
|
|
||
There was a problem hiding this comment.
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?