// streamline.ce — run the full compile + optimize pipeline // // Usage: // cell streamline Full optimized IR as JSON (default) // cell streamline --stats Summary stats per function // cell streamline --ir Human-readable IR // cell streamline --check Warnings only (e.g. high slot count) // cell streamline --types Optimized IR with type annotations // cell streamline --diagnose Run diagnostics (type errors/warnings) var fd = use("fd") var json = use("json") var shop = use("internal/shop") var show_stats = false var show_ir = false var show_check = false var show_types = false var show_diagnose = false var filename = null var i = 0 var di = 0 var diag = null for (i = 0; i < length(args); i++) { if (args[i] == '--stats') { show_stats = true } else if (args[i] == '--ir') { show_ir = true } else if (args[i] == '--check') { show_check = true } else if (args[i] == '--types') { show_types = true } else if (args[i] == '--diagnose') { show_diagnose = true } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] ") $stop() } else if (!starts_with(args[i], '-')) { filename = args[i] } } if (!filename) { print("usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] ") $stop() } // Deep copy mcode for before snapshot (needed by --stats, streamline mutates) var before = null if (show_stats) { before = json.decode(json.encode(shop.mcode_file(filename))) } // For --diagnose, compile with _warn enabled to get diagnostics var optimized = null var compiled = null if (show_diagnose) { compiled = shop.mcode_file(filename) compiled._warn = true optimized = use('streamline')(compiled) } else { optimized = shop.compile_file(filename) } // If no flags, default to full JSON output if (!show_stats && !show_ir && !show_check && !show_types && !show_diagnose) { print(json.encode(optimized, true)) $stop() } // --- Helpers --- var pad_right = function(s, w) { var r = s while (length(r) < w) { r = r + " " } return r } var fmt_val = function(v) { if (is_null(v)) return "null" if (is_number(v)) return text(v) if (is_text(v)) return `"${v}"` if (is_object(v)) return json.encode(v) if (is_logical(v)) return v ? "true" : "false" return text(v) } var count_nops = function(func) { var instrs = func.instructions var nops = 0 var i = 0 if (instrs == null) return 0 while (i < length(instrs)) { if (is_text(instrs[i]) && starts_with(instrs[i], "_nop_")) { nops = nops + 1 } i = i + 1 } return nops } // --- Stats mode --- var ir_stats = null if (show_stats || show_ir) { ir_stats = use("ir_stats") } var print_func_stats = function(func, before_func, name) { var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_slots = func.nr_slots != null ? func.nr_slots : 0 var nr_close = func.nr_close_slots != null ? func.nr_close_slots : 0 var stats = ir_stats.detailed_stats(func) var nops = count_nops(func) var before_stats = before_func ? ir_stats.detailed_stats(before_func) : null var before_total = before_stats ? before_stats.instr : stats.instr print(` ${name}`) print(` args=${text(nr_args)} slots=${text(nr_slots)} close_slots=${text(nr_close)}`) print(` instructions: ${text(stats.instr)} total, ${text(nops)} nops eliminated`) if (before_stats) { print(` before: ${text(before_total)} after: ${text(stats.instr - nops)}`) } print(` load=${text(stats.load)} store=${text(stats.store)} branch=${text(stats.branch)} call=${text(stats.call)}`) print(` guard=${text(stats.guard)} arith=${text(stats.arith)} move=${text(stats.move)} const=${text(stats.const)}`) if (nr_slots > 200) { print(` WARNING: nr_slots=${text(nr_slots)} approaching 255 limit`) } } var print_func_ir = function(func, name) { var ir_text = ir_stats.canonical_ir(func, name, {show_nops: true}) print(ir_text) } var check_func = function(func, name) { var nr_slots = func.nr_slots != null ? func.nr_slots : 0 if (nr_slots > 200) { print(`WARNING: ${name} has ${text(nr_slots)} slots (approaching 255 limit)`) } } // --- Types mode (from dump_types.cm) --- def T_UNKNOWN = "unknown" def T_INT = "int" def T_FLOAT = "float" def T_NUM = "num" def T_TEXT = "text" def T_BOOL = "bool" def T_NULL = "null" def T_ARRAY = "array" def T_RECORD = "record" def T_FUNCTION = "function" def int_result_ops = { bitnot: true, bitand: true, bitor: true, bitxor: true, shl: true, shr: true, ushr: true } def bool_result_ops = { eq_int: true, ne_int: true, lt_int: true, gt_int: true, le_int: true, ge_int: true, eq_float: true, ne_float: true, lt_float: true, gt_float: true, le_float: true, ge_float: true, eq_text: true, ne_text: true, lt_text: true, gt_text: true, le_text: true, ge_text: true, eq_bool: true, ne_bool: true, not: true, and: true, or: true, is_int: true, is_text: true, is_num: true, is_bool: true, is_null: true, is_identical: true, is_array: true, is_func: true, is_record: true, is_stone: true } var access_value_type = function(val) { if (is_number(val)) return is_integer(val) ? T_INT : T_FLOAT if (is_text(val)) return T_TEXT return T_UNKNOWN } var track_types = function(slot_types, instr) { var op = instr[0] var src_type = null if (op == "access") { slot_types[text(instr[1])] = access_value_type(instr[2]) } else if (op == "int") { slot_types[text(instr[1])] = T_INT } else if (op == "true" || op == "false") { slot_types[text(instr[1])] = T_BOOL } else if (op == "null") { slot_types[text(instr[1])] = T_NULL } else if (op == "move") { src_type = slot_types[text(instr[2])] slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN } else if (int_result_ops[op] == true) { slot_types[text(instr[1])] = T_INT } else if (op == "concat") { slot_types[text(instr[1])] = T_TEXT } else if (bool_result_ops[op] == true) { slot_types[text(instr[1])] = T_BOOL } else if (op == "typeof") { slot_types[text(instr[1])] = T_TEXT } else if (op == "array") { slot_types[text(instr[1])] = T_ARRAY } else if (op == "record") { slot_types[text(instr[1])] = T_RECORD } else if (op == "function") { slot_types[text(instr[1])] = T_FUNCTION } else if (op == "invoke" || op == "tail_invoke") { slot_types[text(instr[2])] = T_UNKNOWN } else if (op == "load_field" || op == "load_index" || op == "load_dynamic") { slot_types[text(instr[1])] = T_UNKNOWN } else if (op == "pop" || op == "get") { slot_types[text(instr[1])] = T_UNKNOWN } else if (op == "length") { slot_types[text(instr[1])] = T_INT } else if (op == "add" || op == "subtract" || op == "multiply" || op == "divide" || op == "modulo" || op == "pow" || op == "negate") { slot_types[text(instr[1])] = T_UNKNOWN } return null } var type_annotation = function(slot_types, instr) { var n = length(instr) var parts = [] var j = 1 var v = null var t = null while (j < n - 2) { v = instr[j] if (is_number(v)) { t = slot_types[text(v)] if (t != null && t != T_UNKNOWN) { push(parts, `s${text(v)}:${t}`) } } j = j + 1 } if (length(parts) == 0) return "" return text(parts, " ") } var dump_function_typed = function(func, name) { var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_slots = func.nr_slots != null ? func.nr_slots : 0 var instrs = func.instructions var slot_types = {} var i = 0 var pc = 0 var instr = null var op = null var n = 0 var annotation = null var operand_parts = null var j = 0 var operands = null var pc_str = null var op_str = null var line = null print(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}) ===`) if (instrs == null || length(instrs) == 0) { print(" (empty)") return null } while (i < length(instrs)) { instr = instrs[i] if (is_text(instr)) { if (starts_with(instr, "_nop_")) { i = i + 1 continue } slot_types = {} print(`${instr}:`) } else if (is_array(instr)) { op = instr[0] n = length(instr) annotation = type_annotation(slot_types, instr) operand_parts = [] j = 1 while (j < n - 2) { push(operand_parts, fmt_val(instr[j])) j = j + 1 } operands = text(operand_parts, ", ") pc_str = pad_right(text(pc), 5) op_str = pad_right(op, 14) line = pad_right(` ${pc_str} ${op_str} ${operands}`, 50) if (length(annotation) > 0) { print(`${line} ; ${annotation}`) } else { print(line) } track_types(slot_types, instr) pc = pc + 1 } i = i + 1 } return null } // --- Process functions --- var main_name = optimized.name != null ? optimized.name : "
" var fi = 0 var func = null var bfunc = null var fname = null if (show_stats) { print(`\n--- Stats for ${filename} ---`) } // Main function if (optimized.main != null) { if (show_stats) { print_func_stats(optimized.main, before ? before.main : null, main_name) } if (show_ir) { print_func_ir(optimized.main, main_name) } if (show_check) { check_func(optimized.main, main_name) } if (show_types) { dump_function_typed(optimized.main, main_name) } } // Sub-functions if (optimized.functions != null) { fi = 0 while (fi < length(optimized.functions)) { func = optimized.functions[fi] bfunc = before ? before.functions[fi] : null fname = func.name != null ? func.name : `` if (show_stats) { print_func_stats(func, bfunc, fname) } if (show_ir) { print_func_ir(func, fname) } if (show_check) { check_func(func, fname) } if (show_types) { dump_function_typed(func, fname) } fi = fi + 1 } } if (show_stats) { print('---') } if (show_diagnose) { if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) { di = 0 while (di < length(optimized._diagnostics)) { diag = optimized._diagnostics[di] print(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`) di = di + 1 } print(`\n${text(length(optimized._diagnostics))} diagnostic(s)`) } else { print("No diagnostics.") } } $stop()