inline fns
This commit is contained in:
389
streamline.cm
389
streamline.cm
@@ -2564,6 +2564,337 @@ var streamline = function(ir, log) {
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Sensory function IR synthesis for callback inlining
|
||||
// =========================================================
|
||||
var sensory_opcodes = {
|
||||
is_array: "is_array", is_function: "is_func", is_object: "is_record",
|
||||
is_stone: "is_stone", is_integer: "is_int", is_text: "is_text",
|
||||
is_number: "is_num", is_logical: "is_bool", is_null: "is_null",
|
||||
is_blob: "is_blob", is_data: "is_data",
|
||||
is_true: "is_true", is_false: "is_false", is_fit: "is_fit",
|
||||
is_character: "is_char", is_digit: "is_digit", is_letter: "is_letter",
|
||||
is_lower: "is_lower", is_upper: "is_upper", is_whitespace: "is_ws",
|
||||
is_actor: "is_actor", length: "length"
|
||||
}
|
||||
|
||||
var make_sensory_ir = function(name) {
|
||||
var opcode = sensory_opcodes[name]
|
||||
if (opcode == null) return null
|
||||
return {
|
||||
name: name, nr_args: 1, nr_close_slots: 0, nr_slots: 3,
|
||||
instructions: [[opcode, 2, 1, 0, 0], ["return", 2, 0, 0]]
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Inline eligibility check
|
||||
// =========================================================
|
||||
var prefer_inline_set = {
|
||||
filter: true, every: true, some: true, arrfor: true,
|
||||
reduce: true, array: true
|
||||
}
|
||||
|
||||
var can_inline = function(callee_func, is_prefer) {
|
||||
var instrs = null
|
||||
var i = 0
|
||||
var instr = null
|
||||
var count = 0
|
||||
var limit = 0
|
||||
if (callee_func.nr_close_slots > 0) return false
|
||||
instrs = callee_func.instructions
|
||||
if (instrs == null) return false
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
instr = instrs[i]
|
||||
if (is_array(instr)) {
|
||||
if (instr[0] == "get" || instr[0] == "put") {
|
||||
return false
|
||||
}
|
||||
// Reject if function creates child functions (closures may capture
|
||||
// from the inlined frame, breaking get/put slot references)
|
||||
if (instr[0] == "function") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
if (callee_func.disruption_pc != null && callee_func.disruption_pc > 0) {
|
||||
return false
|
||||
}
|
||||
count = 0
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
if (is_array(instrs[i])) count = count + 1
|
||||
i = i + 1
|
||||
}
|
||||
limit = is_prefer ? 200 : 40
|
||||
return count <= limit
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Pass: inline_calls — inline same-module + sensory functions
|
||||
// =========================================================
|
||||
var inline_counter = 0
|
||||
|
||||
var inline_calls = function(func, ir, log) {
|
||||
var instructions = func.instructions
|
||||
var num_instr = 0
|
||||
var i = 0
|
||||
var j = 0
|
||||
var k = 0
|
||||
var instr = null
|
||||
var op = null
|
||||
var changed = false
|
||||
var inline_count = 0
|
||||
var max_inlines = 20
|
||||
var slot_to_func_idx = {}
|
||||
var slot_to_intrinsic = {}
|
||||
var callee_slot = 0
|
||||
var frame_slot = 0
|
||||
var argc = 0
|
||||
var result_slot = 0
|
||||
var call_start = 0
|
||||
var call_end = 0
|
||||
var arg_slots = null
|
||||
var callee_func = null
|
||||
var is_prefer = false
|
||||
var base = 0
|
||||
var remap = null
|
||||
var cinstr = null
|
||||
var cop = null
|
||||
var new_instr = null
|
||||
var refs = null
|
||||
var label_prefix = null
|
||||
var cont_label = null
|
||||
var spliced = null
|
||||
var before = null
|
||||
var after = null
|
||||
var inlined_body = null
|
||||
var fi = null
|
||||
var intrinsic_name = null
|
||||
|
||||
if (instructions == null) return false
|
||||
num_instr = length(instructions)
|
||||
if (num_instr == 0) return false
|
||||
|
||||
// Build resolution maps
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
if (is_array(instr)) {
|
||||
op = instr[0]
|
||||
if (op == "function") {
|
||||
slot_to_func_idx[text(instr[1])] = instr[2]
|
||||
} else if (op == "access" && is_object(instr[2]) && instr[2].make == "intrinsic") {
|
||||
slot_to_intrinsic[text(instr[1])] = instr[2].name
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Scan for frame/setarg/invoke sequences and inline
|
||||
i = 0
|
||||
while (i < length(instructions)) {
|
||||
instr = instructions[i]
|
||||
if (!is_array(instr) || instr[0] != "frame") {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (inline_count >= max_inlines) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
frame_slot = instr[1]
|
||||
callee_slot = instr[2]
|
||||
argc = instr[3]
|
||||
call_start = i
|
||||
|
||||
// Collect setarg and find invoke
|
||||
arg_slots = array(argc + 1, -1)
|
||||
j = i + 1
|
||||
call_end = -1
|
||||
while (j < length(instructions)) {
|
||||
instr = instructions[j]
|
||||
if (!is_array(instr)) {
|
||||
j = j + 1
|
||||
continue
|
||||
}
|
||||
op = instr[0]
|
||||
if (op == "setarg" && instr[1] == frame_slot) {
|
||||
arg_slots[instr[2]] = instr[3]
|
||||
} else if ((op == "invoke" || op == "tail_invoke") && instr[1] == frame_slot) {
|
||||
result_slot = instr[2]
|
||||
call_end = j
|
||||
j = j + 1
|
||||
break
|
||||
} else if (op == "frame" || op == "goframe") {
|
||||
// Another frame before invoke — abort this pattern
|
||||
break
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
if (call_end < 0) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Resolve callee
|
||||
callee_func = null
|
||||
is_prefer = false
|
||||
|
||||
fi = slot_to_func_idx[text(callee_slot)]
|
||||
if (fi != null && ir.functions != null && fi >= 0 && fi < length(ir.functions)) {
|
||||
callee_func = ir.functions[fi]
|
||||
}
|
||||
|
||||
if (callee_func == null) {
|
||||
intrinsic_name = slot_to_intrinsic[text(callee_slot)]
|
||||
if (intrinsic_name != null) {
|
||||
if (sensory_opcodes[intrinsic_name] != null) {
|
||||
callee_func = make_sensory_ir(intrinsic_name)
|
||||
}
|
||||
if (callee_func != null) {
|
||||
is_prefer = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callee_func == null) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Check eligibility
|
||||
if (!can_inline(callee_func, is_prefer)) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Slot remapping
|
||||
base = func.nr_slots
|
||||
func.nr_slots = func.nr_slots + callee_func.nr_slots
|
||||
remap = array(callee_func.nr_slots, -1)
|
||||
|
||||
// Slot 0 (this) → arg_slots[0] if provided, else allocate a null slot
|
||||
if (length(arg_slots) > 0 && arg_slots[0] >= 0) {
|
||||
remap[0] = arg_slots[0]
|
||||
} else {
|
||||
remap[0] = base
|
||||
}
|
||||
|
||||
// Params 1..nr_args → corresponding arg_slots
|
||||
j = 1
|
||||
while (j <= callee_func.nr_args) {
|
||||
if (j < length(arg_slots) && arg_slots[j] >= 0) {
|
||||
remap[j] = arg_slots[j]
|
||||
} else {
|
||||
remap[j] = base + j
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
// Temporaries → fresh slots
|
||||
j = callee_func.nr_args + 1
|
||||
while (j < callee_func.nr_slots) {
|
||||
remap[j] = base + j
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
// Generate unique label prefix
|
||||
inline_counter = inline_counter + 1
|
||||
label_prefix = "_inl" + text(inline_counter) + "_"
|
||||
cont_label = label_prefix + "cont"
|
||||
|
||||
// Build inlined body with remapping
|
||||
inlined_body = []
|
||||
k = 0
|
||||
while (k < length(callee_func.instructions)) {
|
||||
cinstr = callee_func.instructions[k]
|
||||
|
||||
// Labels (strings that aren't nop markers)
|
||||
if (is_text(cinstr)) {
|
||||
if (starts_with(cinstr, "_nop_")) {
|
||||
inlined_body[] = cinstr
|
||||
} else {
|
||||
inlined_body[] = label_prefix + cinstr
|
||||
}
|
||||
k = k + 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (!is_array(cinstr)) {
|
||||
inlined_body[] = cinstr
|
||||
k = k + 1
|
||||
continue
|
||||
}
|
||||
|
||||
cop = cinstr[0]
|
||||
|
||||
// Handle return → move + jump to continuation
|
||||
if (cop == "return") {
|
||||
new_instr = ["move", result_slot, remap[cinstr[1]], cinstr[2], cinstr[3]]
|
||||
inlined_body[] = new_instr
|
||||
inlined_body[] = ["jump", cont_label, cinstr[2], cinstr[3]]
|
||||
k = k + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Clone and remap the instruction
|
||||
new_instr = array(cinstr)
|
||||
refs = get_slot_refs(cinstr)
|
||||
j = 0
|
||||
while (j < length(refs)) {
|
||||
if (new_instr[refs[j]] >= 0 && new_instr[refs[j]] < length(remap)) {
|
||||
new_instr[refs[j]] = remap[new_instr[refs[j]]]
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
// Remap labels in jump instructions
|
||||
if (cop == "jump" && is_text(cinstr[1]) && !starts_with(cinstr[1], "_nop_")) {
|
||||
new_instr[1] = label_prefix + cinstr[1]
|
||||
} else if ((cop == "jump_true" || cop == "jump_false" || cop == "jump_not_null")
|
||||
&& is_text(cinstr[2]) && !starts_with(cinstr[2], "_nop_")) {
|
||||
new_instr[2] = label_prefix + cinstr[2]
|
||||
}
|
||||
|
||||
// Skip function instructions (don't inline nested function definitions)
|
||||
if (cop == "function") {
|
||||
// Keep the instruction but don't remap func_id — it still refers to ir.functions
|
||||
// Only remap slot position 1 (the destination slot)
|
||||
new_instr = array(cinstr)
|
||||
if (cinstr[1] >= 0 && cinstr[1] < length(remap)) {
|
||||
new_instr[1] = remap[cinstr[1]]
|
||||
}
|
||||
}
|
||||
|
||||
inlined_body[] = new_instr
|
||||
k = k + 1
|
||||
}
|
||||
|
||||
// Add continuation label
|
||||
inlined_body[] = cont_label
|
||||
|
||||
// Splice: replace instructions[call_start..call_end] with inlined_body
|
||||
before = array(instructions, 0, call_start)
|
||||
after = array(instructions, call_end + 1, length(instructions))
|
||||
spliced = array(before, inlined_body)
|
||||
instructions = array(spliced, after)
|
||||
func.instructions = instructions
|
||||
|
||||
changed = true
|
||||
inline_count = inline_count + 1
|
||||
|
||||
// Continue scanning from after the inlined body
|
||||
i = call_start + length(inlined_body)
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Compose all passes
|
||||
// =========================================================
|
||||
@@ -2666,13 +2997,12 @@ var streamline = function(ir, log) {
|
||||
ir._diagnostics = []
|
||||
}
|
||||
|
||||
// Process main function
|
||||
// Phase 1: Optimize all functions (bottom-up, existing behavior)
|
||||
if (ir.main != null) {
|
||||
optimize_function(ir.main, log)
|
||||
insert_stone_text(ir.main, log)
|
||||
}
|
||||
|
||||
// Process all sub-functions (resolve closure types from parent first)
|
||||
var fi = 0
|
||||
if (ir.functions != null) {
|
||||
fi = 0
|
||||
@@ -2684,7 +3014,60 @@ var streamline = function(ir, log) {
|
||||
}
|
||||
}
|
||||
|
||||
// Compress slots across all functions (must run after per-function passes)
|
||||
// Phase 2: Inline pass
|
||||
var changed_main = false
|
||||
var changed_fns = null
|
||||
if (ir.main != null) {
|
||||
changed_main = inline_calls(ir.main, ir, log)
|
||||
}
|
||||
if (ir.functions != null) {
|
||||
changed_fns = array(length(ir.functions), false)
|
||||
fi = 0
|
||||
while (fi < length(ir.functions)) {
|
||||
changed_fns[fi] = inline_calls(ir.functions[fi], ir, log)
|
||||
fi = fi + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Re-optimize inlined functions
|
||||
if (changed_main) {
|
||||
optimize_function(ir.main, log)
|
||||
insert_stone_text(ir.main, log)
|
||||
}
|
||||
if (ir.functions != null) {
|
||||
fi = 0
|
||||
while (fi < length(ir.functions)) {
|
||||
if (changed_fns != null && changed_fns[fi]) {
|
||||
optimize_function(ir.functions[fi], log)
|
||||
insert_stone_text(ir.functions[fi], log)
|
||||
}
|
||||
fi = fi + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Cascade — second inline round (callbacks inside inlined bodies)
|
||||
if (changed_main) {
|
||||
changed_main = inline_calls(ir.main, ir, log)
|
||||
if (changed_main) {
|
||||
optimize_function(ir.main, log)
|
||||
insert_stone_text(ir.main, log)
|
||||
}
|
||||
}
|
||||
if (ir.functions != null) {
|
||||
fi = 0
|
||||
while (fi < length(ir.functions)) {
|
||||
if (changed_fns != null && changed_fns[fi]) {
|
||||
changed_fns[fi] = inline_calls(ir.functions[fi], ir, log)
|
||||
if (changed_fns[fi]) {
|
||||
optimize_function(ir.functions[fi], log)
|
||||
insert_stone_text(ir.functions[fi], log)
|
||||
}
|
||||
}
|
||||
fi = fi + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5: Compress slots across all functions (must run after per-function passes)
|
||||
compress_slots(ir)
|
||||
|
||||
// Expose DEF/USE functions via log if requested
|
||||
|
||||
Reference in New Issue
Block a user