From f4bd723d929e53475f7990374dca7b7dfcbbdc42 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sun, 1 Nov 2026 10:33:55 -0800 Subject: Add Chapter 29 for tech debt cleanup with the parser split Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks. --- .../test/java/com/seaofnodes/simple/Simple.java | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 chapter29/src/test/java/com/seaofnodes/simple/Simple.java (limited to 'chapter29/src/test/java/com/seaofnodes/simple/Simple.java') diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Simple.java b/chapter29/src/test/java/com/seaofnodes/simple/Simple.java new file mode 100644 index 00000000..1f1e8ab0 --- /dev/null +++ b/chapter29/src/test/java/com/seaofnodes/simple/Simple.java @@ -0,0 +1,264 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.CompUnit; +import com.seaofnodes.simple.codegen.CodeGen.Phase; +import com.seaofnodes.simple.print.*; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.Utils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class Simple { + static void usage() { + System.out.println( +""" +simple [options] input-file.smp ...program args... +Options are applied in-order. +Options: + --help - This text + + --root root/ - Project root directory; this must end in a '/' and input + files are relative paths to this. If missing, the one input + file path is treated as its own root directory. + + -o output - Project output directory; similar to --root, if 'output' ends in a + '/', then all result files use relative paths to 'output', using a input file + root-relative path. If --root is missing, the one result file is named 'output' + + -L path - add an external Simple object search path + --eval - (slowly) evaluate the compiled code in emulator + --run - run the compiled code natively; this is the default + --main - export the top-level initializer as C `main` + --cpu - use specific CPU (x86_64_v2, riscv, arm) + --abi - use specific ABI variant (SystemV) + --target - print native CPU and ABI + --version + -S - dump generated assembler code + --dump-size - print the size of generated code + --dump-time - print compilation and execution times + --dump-after-parse - dump intermediate representation after parse + --dump-after-iter - dump intermediate representation after pessimistic iter pass + --dump-after-opto - dump intermediate representation after SCCP + --dump-after-iter-post-opto - dump after SCCP-driven peepholes and inlining + --dump-after-finalize-ir - dump after field layout and IR finalization + --dump-after-type-check - dump intermediate representation after type check pass + --dump-after-lower - dump expanded class initialization before loop trees + --dump-after-select - dump intermediate representation after instruction selection pass + --dump-after-gcm - dump intermediate representation after GCM pass + --dump-after-local-sched - dump intermediate representation after local scheduling pass + --dump-after-reg-alloc - dump intermediate representation after register allocation pass + --dump-after-encode - dump intermediate representation after encoding pass + --dump-after-all - dump intermediate representation after all passes + --dump - dump final intermediate representation only +""" +); + System.exit(0); + } + + static void bad_usage() { throw bad("Invalid usage (use --help)"); } + static RuntimeException bad(String err) { + System.err.println("ERROR: "+err); + System.exit(1); + return new RuntimeException(err); + } + + static void target() { + System.out.print(TestC.CPU_PORT); + System.out.print("-"); + System.out.println(TestC.CALL_CONVENTION); + } + + static void print_compilation_times(CodeGen code) { + long[] times = code._times; + for( int i=0; i libPaths = new ArrayList<>(); + + // Parse command line + int i; for( i = 0; i < args.length; i++ ) { + String arg = args[i]; + if( arg.charAt(0) != '-' ) { + input_filename = arg; + break; + } + + switch (arg) { + case "-": input_filename=""; break; + case "--dump-after-parse": dump |= 1<= args.length || args[i + 1].charAt(0) == '-') bad_usage(); + out = args[++i]; do_codegen=true; + break; + case "--root": if( root!=null || i+1>=args.length ) bad_usage(); + root=args[++i]; + break; + case "-L": if (i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage(); + libPaths.add(args[++i]); + break; + case "--cpu": if (cpu != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage(); + cpu = args[++i]; + break; + case "--abi": if (abi != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage(); + abi = args[++i]; + break; + case "--target": target(); break; + case "--version": throw Utils.TODO(); + case "--help": usage(); + default: bad_usage(); + } + } + + // Break the paths into these parts: + // - A module path, given from "--root" or inferred from the one input file + // - Some sources [modpath/relpath/file.smp]*, all relative to the module path. + // - A build path, given from "-o" or inferred from the one input file + // + // All the sources are compiled, with outputs in [buildpath/relpath/file.o]* + + if (input_filename == null) throw bad("no input file' (use --help)"); + if( !input_filename.isEmpty() && !input_filename.endsWith(".smp") ) + throw bad("File extension must be .smp"); + Path inPath = Path.of(input_filename); + + // Figure out the module path + Path modPath; + if( root == null ) { + modPath = inPath.getParent(); + if( modPath == null ) + modPath = Path.of("."); + } else { + modPath=Path.of(root); + inPath=modPath.resolve(inPath); + } + + // Compute the module-relative paths for all sources + Path srcRelPath = modPath.relativize(inPath); + int n = srcRelPath.getNameCount(); + Path relPath = n==1 ? null : srcRelPath.subpath(0,n-1); + // Compute source/class name + String srcNameExt = srcRelPath.getName(n-1).toString(); + String srcName = srcRelPath.toString().replace('\\','/'); + srcName=srcName.substring(0,srcName.length()-4); + + // Compute the build path. + Path outPath; + if( out == null ) { + // If no output given, the build dir is the modPath + outPath = modPath; + } else { + // If the output is a directory, that is our out path + outPath = Path.of(out); + if( !out.endsWith("/") ) { + // If the output is a file, the directory is our outPath + Path fullOut = outPath; + outPath = outPath.getParent(); + if( outPath == null ) + outPath = Path.of("."); + Path foo = outPath.relativize(fullOut); + if( !foo.toString().equals(srcName+".o") ) + throw bad("Output path needs to be of the form "+srcName+".o"); + } + } + String outBase = outPath.resolve(srcName).toString(); + + if( do_run || print_asm || print_size ) { + if (cpu == null) cpu = TestC.CPU_PORT; + if (abi == null) abi = TestC.CALL_CONVENTION; + do_codegen = true; + } + if( cpu == null ) cpu = TestC.CPU_PORT; + if( abi == null ) abi = TestC.CALL_CONVENTION; + + String simpleHome = System.getenv("SIMPLE_HOME"); + if( simpleHome != null && !simpleHome.isEmpty() ) + libPaths.add(Path.of(simpleHome,cpu+"_"+abi).toString()); + + // Keep file-backed units so discovery sees parents and siblings. + src = input_filename.isEmpty() ? getInput() : null; + + // Compilation pipeline + Ary externPaths = libPaths.isEmpty() ? null : new Ary<>(libPaths.toArray(new String[0])); + CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), externPaths, srcName, src, 456, true, TypeInteger.BOT ); + code.driver(do_eval && !do_codegen ? Phase.TypeCheck : Phase.LastPhase, + cpu,abi, false, emit_main || do_run || do_eval, dump); + + if( do_codegen && print_asm ) + System.out.println(code.asm()); + + if( print_size ) + System.out.printf( "Code Size: %d%n", code._encoding._bits.size()); + + if( print_time ) + print_compilation_times(code); + + if( do_eval ) { + // TODO: Support for evaluation of functions with different argument numbers and types + long t = System.currentTimeMillis(); + long arg = (i+1 < args.length) ? Integer.parseInt(args[i+1]) : 0; + System.out.println(Eval2.eval(code, arg, 100000)); + if( print_time ) + System.out.printf( "EXECUTION TIME: %.3f sec%n", (System.currentTimeMillis() - t) / 1e3); + } + if( do_run ) { + if ( !TestC.CPU_PORT.equals( cpu ) || !TestC.CALL_CONVENTION.equals( abi ) ) + throw bad("cannot run code on not native target"); + String exe = TestC.OS.startsWith("Windows") ? outBase+".exe" : outBase; + String result = TestC.gcc(code, null, null, "", exe); + System.out.print(result); + } + } +} -- cgit v1.2.3