diff --git a/scripts/check_java_inline_budget.py b/scripts/check_java_inline_budget.py index f4d0dfbb7d..f91800b132 100755 --- a/scripts/check_java_inline_budget.py +++ b/scripts/check_java_inline_budget.py @@ -43,19 +43,50 @@ def die(msg: str): sys.exit(1) -def disassemble(classes: str) -> str: - """One javap call for both frames -- two would be two JVM startups.""" +def disassemble(classes: str) -> dict: + """One javap call for both frames -- two would be two JVM startups. + + Returned per class, NOT as one blob: the two signatures are distinct today + only by luck, and a search over the concatenation would happily answer for + the wrong class. javap also exits 0 when only SOME of the named classes + resolve, reporting the rest on stderr, so its status says nothing and the + stderr has to be read. + """ names = ["io.github.talib." + c for c, _ in FRAMES] try: - return subprocess.run(["javap", "-p", "-c", "-cp", classes] + names, - capture_output=True, text=True, check=True).stdout - except (subprocess.CalledProcessError, FileNotFoundError) as e: - die("javap could not read %s from %s: %s" % (", ".join(names), classes, e)) - - -def code_length(out: str, cls: str, sig: re.Pattern) -> int: - lines = out.splitlines() - start = next((i for i, l in enumerate(lines) if sig.match(l)), None) + p = subprocess.run(["javap", "-p", "-c", "-cp", classes] + names, + capture_output=True, text=True) + except FileNotFoundError as e: + die("javap is not on PATH: %s" % e) + if p.returncode != 0: + die("javap failed on %s: %s" % (classes, p.stderr.strip() or p.stdout.strip())) + if p.stderr.strip(): + die("javap could not read every class from %s -- it exits 0 for this, so " + "the gate must reject it explicitly: %s" % (classes, p.stderr.strip())) + + # Split on the class header javap emits once per class. + sections, cur = {}, None + for line in p.stdout.splitlines(): + m = re.match(r"(?:public |final |abstract )*class io\.github\.talib\.(\S+) ", line) + if m: + cur = m.group(1).rstrip("{").strip() + sections[cur] = [] + elif cur is not None: + sections[cur].append(line) + for cls, _ in FRAMES: + if cls not in sections: + die("javap printed no section for io.github.talib.%s -- it was asked " + "for it and did not refuse, so the disassembly parse moved." % cls) + return sections + + +def code_length(sections: dict, cls: str, sig: re.Pattern) -> int: + lines = sections[cls] + matches = [i for i, l in enumerate(lines) if sig.match(l)] + if len(matches) > 1: + die("%s: %d methods match %s -- the gate cannot tell which frame it is " + "measuring." % (cls, len(matches), sig.pattern)) + start = matches[0] if matches else None if start is None: die("%s: no method matching %s -- the signature moved, so this gate " "measured NOTHING. Fix the pattern in this script rather than " @@ -82,10 +113,10 @@ def main(): die("usage: check_java_inline_budget.py ") classes = sys.argv[1] - out = disassemble(classes) + sections = disassemble(classes) over = [] for cls, sig in FRAMES: - n = code_length(out, cls, sig) + n = code_length(sections, cls, sig) name = "%s.%s" % (cls, "peek" if "peek" in sig.pattern else "maStepImpl") print("%-24s %3d bytes (budget %d, %+d)" % (name, n, BUDGET, n - BUDGET)) if n > BUDGET: diff --git a/ta_codegen/generator/src/backends/java_stream.rs b/ta_codegen/generator/src/backends/java_stream.rs index a2a2f13ab5..1546eb4f54 100644 --- a/ta_codegen/generator/src/backends/java_stream.rs +++ b/ta_codegen/generator/src/backends/java_stream.rs @@ -3107,14 +3107,17 @@ fn emit_dispatch( } } } - // `return`, not `break`: the switch is the whole method body, so this - // costs a byte where the jump to the end cost three, and the step frame - // is 4 bytes over the same 325-byte budget the peek frame is kept under. + // Every arm returns, including `default` below, which is what makes + // javac reject anything emitted after this switch. Nothing may go + // there: the arms are the only writers of `sp.cur_*` and a tail would + // be dead on every real MAType, reached only by the unreachable + // default. Keep the two in step -- a `break` default silently restores + // the trap, at no saving. let _ = writeln!(o, " return;"); let _ = writeln!(o, " }}"); } let _ = writeln!(o, " default:"); - let _ = writeln!(o, " break; /* unreachable: open rejects arms without a sub-stream */"); + let _ = writeln!(o, " return; /* unreachable: open rejects arms without a sub-stream */"); let _ = writeln!(o, " }}"); let _ = writeln!(o, " }}"); diff --git a/ta_codegen/output/java/fragments/Core_MA.java b/ta_codegen/output/java/fragments/Core_MA.java index da8f0f2fd7..56610557b2 100644 --- a/ta_codegen/output/java/fragments/Core_MA.java +++ b/ta_codegen/output/java/fragments/Core_MA.java @@ -826,7 +826,7 @@ void maStepImpl( MaStream sp, double inReal ) return; } default: - break; /* unreachable: open rejects arms without a sub-stream */ + return; /* unreachable: open rejects arms without a sub-stream */ } } private RetCode maOpenImpl( MaStream sp, double inReal[], int startIdx, int optInTimePeriod, MAType optInMAType ) diff --git a/ta_codegen/output/java/library/src/main/java/io/github/talib/BuildStamp.java b/ta_codegen/output/java/library/src/main/java/io/github/talib/BuildStamp.java index 176c18705d..cfc4355017 100644 --- a/ta_codegen/output/java/library/src/main/java/io/github/talib/BuildStamp.java +++ b/ta_codegen/output/java/library/src/main/java/io/github/talib/BuildStamp.java @@ -9,7 +9,7 @@ */ public final class BuildStamp { /** Digest of the generated {@code Core} method text this build carries. */ - public static final String GENCODE_DIGEST = "5582353372fbe611"; + public static final String GENCODE_DIGEST = "854b585951e2c854"; private BuildStamp() { } diff --git a/ta_codegen/output/java/library/src/main/java/io/github/talib/Core.java b/ta_codegen/output/java/library/src/main/java/io/github/talib/Core.java index b21be47637..7181248dcc 100644 --- a/ta_codegen/output/java/library/src/main/java/io/github/talib/Core.java +++ b/ta_codegen/output/java/library/src/main/java/io/github/talib/Core.java @@ -113838,7 +113838,7 @@ void maStepImpl( MaStream sp, double inReal ) return; } default: - break; /* unreachable: open rejects arms without a sub-stream */ + return; /* unreachable: open rejects arms without a sub-stream */ } } private RetCode maOpenImpl( MaStream sp, double inReal[], int startIdx, int optInTimePeriod, MAType optInMAType ) diff --git a/ta_codegen/output/java/tools/TaCodegenServe.java b/ta_codegen/output/java/tools/TaCodegenServe.java index f9c6df0988..a4ffe0d9d3 100644 --- a/ta_codegen/output/java/tools/TaCodegenServe.java +++ b/ta_codegen/output/java/tools/TaCodegenServe.java @@ -113509,7 +113509,7 @@ void maStepImpl( MaStream sp, double inReal ) return; } default: - break; /* unreachable: open rejects arms without a sub-stream */ + return; /* unreachable: open rejects arms without a sub-stream */ } } private RetCode maOpenImpl( MaStream sp, double inReal[], int startIdx, int optInTimePeriod, MAType optInMAType ) @@ -182174,7 +182174,7 @@ public ZlemaStream zlemaOpenAndFill( double inReal[], int optInTimePeriod, doubl public class TaCodegenServe { static Core core = new Core(); - static final String SPLICED_GENCODE_DIGEST = "5582353372fbe611"; + static final String SPLICED_GENCODE_DIGEST = "854b585951e2c854"; static final int MAX_ARRAY_SIZE = 200000; static double[] refOpen = new double[MAX_ARRAY_SIZE]; static double[] refHigh = new double[MAX_ARRAY_SIZE];