Skip to content

Commit

Permalink
Reduce final binary size by *a lot*
Browse files Browse the repository at this point in the history
  • Loading branch information
Kruhlmann committed Nov 18, 2024
1 parent 8fc6b88 commit 4f2feab
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 54 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use nix
11 changes: 11 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) Siemens Mobility A/S 2024, All Rights Reserved - CONFIDENTIAL
{ pkgs ? import <nixpkgs> { } }:

pkgs.mkShell {
buildInputs = [
pkgs.nodejs_22
pkgs.nodePackages.pnpm
pkgs.nodePackages.vscode-json-languageserver
pkgs.nasm
];
}
27 changes: 14 additions & 13 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}

Expand Down
9 changes: 7 additions & 2 deletions src/compiler/alchemy/amd64/amd64.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -45,12 +47,15 @@ ret`;

public constructor(
protected lexer: Lexer<AlchemySource, Instruction[]>,
protected pruner: InstructionsPruner,
protected cross_referencer: CrossReferencer,
) {}
) { }

public compile(source: AlchemySource): CompilationResult<Instruction[], string> {
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");
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/nasm/nasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class NasmCompiler implements Compiler<NasmCompilerParameters, string, st
public compile(parameters: NasmCompilerParameters): CompilationResult<string, string> {
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") {
Expand All @@ -29,9 +30,9 @@ export class NasmCompiler implements Compiler<NasmCompilerParameters, string, st
throw new Error(`${this.constructor.name}: ${chmod_result.stderr}`);
}

Logger.debug(`Removing ${asm_file}`);
fs.unlinkSync(asm_file);
Logger.debug(`Removing ${object_file}`);
Logger.debug(`Unlink ${asm_file}`);
//fs.unlinkSync(asm_file);
Logger.debug(`Unlink ${object_file}`);
fs.unlinkSync(object_file);

return {
Expand Down
8 changes: 6 additions & 2 deletions src/include/preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { Logger } from "../logger";
import { ModuleNotFoundError } from "./module_not_found_error";

export class IncludePreprocessor {
public constructor(protected default_include_paths: string[]) {}
protected include_cache: Record<string, string>;

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 {
Expand All @@ -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();
Expand All @@ -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);
}
Expand Down
134 changes: 134 additions & 0 deletions src/include_pruner/include_pruner.ts
Original file line number Diff line number Diff line change
@@ -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<string, { start: number; end: number }> = 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<string>();
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<string>,
subroutine_map: Map<string, { start: number; end: number }>,
instructions: Instruction[],
all_subroutine_names: Set<string>,
): 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
);
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/include_pruner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./include_pruner";
export * from "./pruner";
5 changes: 5 additions & 0 deletions src/include_pruner/pruner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Instruction } from "../instruction";

export interface InstructionsPruner {
prune(instructions: Instruction[]): Instruction[];
}
22 changes: 0 additions & 22 deletions std/iotmp.alc

This file was deleted.

24 changes: 24 additions & 0 deletions std/size.alc
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 4 additions & 8 deletions std/str.alc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include std.bool
include std.size

sub STR_NEWLINE "\n" swap marine
sub STR_HEXCHARS_UPPER "0123456789ABCDEF" swap marine
Expand Down Expand Up @@ -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

Expand Down
10 changes: 7 additions & 3 deletions std/sys/mem.alc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include std.io
include std.math
include std.size
include std.sys.call

sub BLOCK_FREE 0 swap marine
Expand Down Expand Up @@ -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 + !
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion syntax/vim/alchemy.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4f2feab

Please sign in to comment.