diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..cac906b --- /dev/null +++ b/shell.nix @@ -0,0 +1,11 @@ +# Copyright (c) Siemens Mobility A/S 2024, All Rights Reserved - CONFIDENTIAL +{ pkgs ? import { } }: + +pkgs.mkShell { + buildInputs = [ + pkgs.nodejs_22 + pkgs.nodePackages.pnpm + pkgs.nodePackages.vscode-json-languageserver + pkgs.nasm + ]; +} diff --git a/src/cli.ts b/src/cli.ts index 8cd8f9d..684ba9f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,29 +3,30 @@ import fs from "node:fs"; import { Amd64AlchemyCompiler, NasmCompiler } from "./compiler"; import { CrossReferencer } from "./cross_referencer"; import { IncludePreprocessor } from "./include"; +import { IncludeInstructionPruner } from "./include_pruner"; import { AlchemyLexer } from "./lexer"; import { Logger } from "./logger"; export class AlchemyCompilerCli { - public compile(source_file: string | undefined, output_file: string) { - Logger.silent = false; - if (source_file === undefined || output_file === undefined) { + public compile(source_file?: string, output_file?: string) { + if (!source_file || !output_file) { return this.usage(); } + const includes = [process.cwd(), "/usr/share/alchemy"]; + Logger.silent = false; + Logger.debug(`Compiling ${source_file}`); const raw_source = fs.readFileSync(source_file).toString(); - const source = new IncludePreprocessor([process.cwd(), "/usr/share/alchemy"]).resolve_includes(raw_source); + Logger.debug(`Include directories: [${includes.join(",")}]`); + const source = new IncludePreprocessor(includes).resolve_includes(raw_source); const lexer = new AlchemyLexer(); const cross_referencer = new CrossReferencer(); - const alchemy_compiler = new Amd64AlchemyCompiler(lexer, cross_referencer); + const pruner = new IncludeInstructionPruner(); + const alchemy_compiler = new Amd64AlchemyCompiler(lexer, pruner, cross_referencer); + const compilation_result = alchemy_compiler.compile({ text: source, context: source_file }); + const nasm_compiler = new NasmCompiler(); - const compilation_result = alchemy_compiler.compile({ - text: source, - context: source_file, - }); - const nasm_result = nasm_compiler.compile({ - asm_source: compilation_result.output, - output_file, - }); + const nasm_result = nasm_compiler.compile({ asm_source: compilation_result.output, output_file }); + Logger.info(`Compiled to file ${nasm_result.output}`); } diff --git a/src/compiler/alchemy/amd64/amd64.ts b/src/compiler/alchemy/amd64/amd64.ts index 5cfc6df..d4834ad 100644 --- a/src/compiler/alchemy/amd64/amd64.ts +++ b/src/compiler/alchemy/amd64/amd64.ts @@ -1,6 +1,8 @@ import { CrossReferencer } from "../../../cross_referencer"; +import { InstructionsPruner } from "../../../include_pruner"; import { Instruction } from "../../../instruction"; import { AlchemySource, Lexer } from "../../../lexer"; +import { Logger } from "../../../logger"; import { CompilationResult } from "../../result"; import { AlchemyCompiler } from "../alchemy"; @@ -45,12 +47,15 @@ ret`; public constructor( protected lexer: Lexer, + protected pruner: InstructionsPruner, protected cross_referencer: CrossReferencer, - ) {} + ) { } public compile(source: AlchemySource): CompilationResult { + Logger.debug(`Compiling AMD64 assembly from ${source.text.length} bytes`); const instructions = this.lexer.lex(source); - const cross_referenced_instructions = this.cross_referencer.cross_reference_instructions(instructions); + const pruned_instructions = this.pruner.prune(instructions); + const cross_referenced_instructions = this.cross_referencer.cross_reference_instructions(pruned_instructions); const instructions_source = cross_referenced_instructions .map((instruction, index) => instruction.to_asm(index)) .join("\n"); diff --git a/src/compiler/nasm/nasm.ts b/src/compiler/nasm/nasm.ts index 5cf2d55..7e608c3 100644 --- a/src/compiler/nasm/nasm.ts +++ b/src/compiler/nasm/nasm.ts @@ -15,6 +15,7 @@ export class NasmCompiler implements Compiler { const asm_file = `${parameters.output_file}.asm`; const object_file = `${parameters.output_file}.o`; + Logger.debug(`Writing NASM-style assembly to ${asm_file}`); fs.writeFileSync(asm_file, parameters.asm_source); const nasm_result = new BinaryRuntime("nasm", ["-felf64", asm_file]).run(); if (nasm_result.exit_code !== "0") { @@ -29,9 +30,9 @@ export class NasmCompiler implements Compiler; + + public constructor(protected default_include_paths: string[]) { + this.include_cache = {}; + } // eslint-disable-next-line complexity public resolve_includes(source_code: string, included_files: string[] = []): string { @@ -21,7 +25,6 @@ export class IncludePreprocessor { const module_path = target_module.replace(/\./g, "/"); for (const base_path of this.default_include_paths) { - Logger.debug(`Looking for module ${target_module} in ${base_path}`); const module_path_candidate = path.join(base_path, module_path); const is_directory = fs.existsSync(module_path_candidate) && fs.lstatSync(module_path_candidate).isDirectory(); @@ -48,6 +51,7 @@ export class IncludePreprocessor { // ignoring file that has already been included return ""; } else { + Logger.debug(`Including ${module_path_candidate}.alc`) included_files.push(`${module_path_candidate}.alc`); return this.resolve_includes(fs.readFileSync(`${module_path_candidate}.alc`).toString(), included_files); } diff --git a/src/include_pruner/include_pruner.ts b/src/include_pruner/include_pruner.ts new file mode 100644 index 0000000..707b449 --- /dev/null +++ b/src/include_pruner/include_pruner.ts @@ -0,0 +1,134 @@ +import { Instruction, LiteralInstruction, UnreferencedSubInstruction, MarineInstruction } from "../instruction"; +import { Logger } from "../logger"; +import { InstructionsPruner } from "./pruner"; + +export class IncludeInstructionPruner implements InstructionsPruner { + public prune(instructions: Instruction[]): Instruction[] { + const subroutine_map: Map = new Map(); + const total_instructions = instructions.length; + let i = 0; + + while (i < total_instructions) { + if (instructions[i] instanceof UnreferencedSubInstruction) { + if ( + i + 1 < total_instructions && + instructions[i + 1] instanceof LiteralInstruction + ) { + const sub_name = (instructions[i + 1] as LiteralInstruction).read_argument(); + const start = i; + + let end = total_instructions - 1; + for (let j = i + 2; j < total_instructions; j++) { + if (instructions[j] instanceof MarineInstruction) { + end = j; + break; + } + } + + subroutine_map.set(sub_name, { start, end }); + Logger.debug(`Subroutine '${sub_name}' found from index ${start} to ${end}`); + + i = end + 1; + continue; + } else { + throw new Error( + `Invalid subroutine definition starting at index ${i}` + ); + } + } else { + i++; + } + } + + const all_subroutine_names = new Set(subroutine_map.keys()); + + const reachable_subroutines = new Set(); + if (!subroutine_map.has("main")) { + Logger.error("No main function found"); + throw new Error("No main function found"); + } + + this.collect_reachable_subroutines( + "main", + reachable_subroutines, + subroutine_map, + instructions, + all_subroutine_names + ); + + const pruned_instructions: Instruction[] = []; + i = 0; + + while (i < total_instructions) { + if (instructions[i] instanceof UnreferencedSubInstruction) { + if ( + i + 1 < total_instructions && + instructions[i + 1] instanceof LiteralInstruction + ) { + const sub_name = (instructions[i + 1] as LiteralInstruction).read_argument(); + + if (reachable_subroutines.has(sub_name)) { + const sub_info = subroutine_map.get(sub_name)!; + for (let j = sub_info.start; j <= sub_info.end; j++) { + pruned_instructions.push(instructions[j]); + } + i = sub_info.end + 1; + continue; + } else { + Logger.debug(`Pruning unreachable subroutine '${sub_name}'`); + const sub_info = subroutine_map.get(sub_name)!; + i = sub_info.end + 1; + continue; + } + } else { + pruned_instructions.push(instructions[i]); + i++; + } + } else { + pruned_instructions.push(instructions[i]); + i++; + } + } + + return pruned_instructions; + } + + protected collect_reachable_subroutines( + current_sub_name: string, + visited: Set, + subroutine_map: Map, + instructions: Instruction[], + all_subroutine_names: Set, + ): void { + if (visited.has(current_sub_name)) { + return; + } + visited.add(current_sub_name); + + const sub_info = subroutine_map.get(current_sub_name); + if (!sub_info) { + throw new Error(`Subroutine '${current_sub_name}' is called but not defined`); + } + + for (let idx = sub_info.start + 2; idx <= sub_info.end; idx++) { + if ( + instructions[idx] instanceof LiteralInstruction && + !( + idx - 1 >= 0 && + instructions[idx - 1] instanceof UnreferencedSubInstruction + ) + ) { + const literal_arg = (instructions[idx] as LiteralInstruction).read_argument(); + if (all_subroutine_names.has(literal_arg)) { + this.collect_reachable_subroutines( + literal_arg, + visited, + subroutine_map, + instructions, + all_subroutine_names + ); + } + } + } + } +} diff --git a/src/include_pruner/index.ts b/src/include_pruner/index.ts new file mode 100644 index 0000000..6b99b51 --- /dev/null +++ b/src/include_pruner/index.ts @@ -0,0 +1,2 @@ +export * from "./include_pruner"; +export * from "./pruner"; diff --git a/src/include_pruner/pruner.ts b/src/include_pruner/pruner.ts new file mode 100644 index 0000000..a412aa7 --- /dev/null +++ b/src/include_pruner/pruner.ts @@ -0,0 +1,5 @@ +import { Instruction } from "../instruction"; + +export interface InstructionsPruner { + prune(instructions: Instruction[]): Instruction[]; +} diff --git a/std/iotmp.alc b/std/iotmp.alc deleted file mode 100644 index d604667..0000000 --- a/std/iotmp.alc +++ /dev/null @@ -1,22 +0,0 @@ -include std.math -include std.sys.call -include std.io -include std.sys.halloc - -sub printx - swap - 1024 halloc - 0 while clone 1024 < do - over 15 & STR_HEXCHARS_UPPER prune_strlen + - over curheap swap - swap write - swap 4 >> swap - 64 + - wend - "0x" print - 16 while clone 0 > do - clone 64 mul curheap swap - readq 1 swap print - -- - wend - "\n" print - drop drop drop -marine diff --git a/std/size.alc b/std/size.alc index 430a7ab..189bd78 100644 --- a/std/size.alc +++ b/std/size.alc @@ -3,3 +3,27 @@ include std.math sub BYTES_IN_QWORD 8 swap marine sub size_q swap BYTES_IN_QWORD mul swap marine + +[Maximum u16 value] +[ret:int Maximum value (2^16-1) 0xFFFF] +sub u16_max 65535 swap marine + +[Maximum u32 value] +[ret:int Maximum value (2^32-1) 0xFFFFFFFF] +sub u32_max 4294967295 swap marine + +[Maximum u64 value] +[ret:int Maximum value (2^64-1) 0xFFFFFFFFFFFFFFFF] +sub u64_max 18446744073709551615 swap marine + +[Maximum i16 value] +[ret:int Maximum value (2^16/2-1) 0x7FFF] +sub i16_max 32767 swap marine + +[Maximum i32 value] +[ret:int Maximum value (2^32/2-1) 0x7FFFFFFF] +sub i32_max 2147483647 swap marine + +[Maximum i64 value] +[ret:int Maximum value (2^64/2-1) 0x7FFFFFFFFFFFFFFF] +sub i64_max 9223372036854775807 swap marine diff --git a/std/str.alc b/std/str.alc index 18a8316..c8967fd 100644 --- a/std/str.alc +++ b/std/str.alc @@ -1,4 +1,5 @@ include std.bool +include std.size sub STR_NEWLINE "\n" swap marine sub STR_HEXCHARS_UPPER "0123456789ABCDEF" swap marine @@ -76,14 +77,9 @@ marine [ret:void] sub printx swap - clone 2147483647 [32 bit signed max] > if - printx_64 - else - clone 65535 [16 bit signed max] > if - printx_32 - else - printx_16 - endif + clone u32_max > if printx_64 else + clone u16_max > if printx_32 + else printx_16 endif endif marine diff --git a/std/sys/mem.alc b/std/sys/mem.alc index 49e171b..d49e944 100644 --- a/std/sys/mem.alc +++ b/std/sys/mem.alc @@ -1,5 +1,6 @@ include std.io include std.math +include std.size include std.sys.call sub BLOCK_FREE 0 swap marine @@ -50,12 +51,12 @@ sub malloc heap_start while 1 do clone ? 1 & BLOCK_FREE != if - clone ? 18446744073709551614 & over + swap drop [Add the node size to the current node address] + clone ? u64_max & over + swap drop [Add the node size to the current node address] else - clone ? 18446744073709551614 & + clone ? u64_max & swap rev3 2clone > if swap drop swap - clone ? 18446744073709551614 & over + swap drop [Add the node size to the current node address] + clone ? u64_max & over + swap drop [Add the node size to the current node address] else swap drop over swap BLOCK_USED + ! @@ -89,6 +90,9 @@ sub mwalk wend marine +[Initializes the memory allocator] +[arg:ptr pointer to memory block] +[ret:void] sub meminit "Initializing heap allocator @ " print heap_end printx heap_end 32 + brk drop [Allocate 32 bytes on the heap for metadata] diff --git a/syntax/vim/alchemy.vim b/syntax/vim/alchemy.vim index cb16eb9..61fe30a 100644 --- a/syntax/vim/alchemy.vim +++ b/syntax/vim/alchemy.vim @@ -15,7 +15,7 @@ syn match alchemyBin "\<[01]\+B\>" hi link alchemyKeyword Keyword hi link alchemyOperator Keyword -hi link alchemyKeywordBlock Operator +hi link alchemyKeywordBlock Keyword hi link alchemyDirective Statement hi link alchemyLibrary Include hi link alchemyDocstring Comment