skip to content

What must a rewritten compiled unit still satisfy for the runtime to accept it and run the edited method?

level: middleimportance: should knowfreq 40%

answer

  1. the unit declares things about itself
  2. surface fixed, body edited
  3. counts must match the instructions
  4. working space and local slots
  5. refused on load, not on edit

basics

~20 s

A rewritten unit must stay well-formed: the declared surface intact so existing callers still resolve, and the body's bookkeeping - working space, local slots, exception ranges, types on every path - matching the instructions actually present.

solid answer

~40 s

A compiled unit is not free-form text; it carries declarations the runtime checks before it will run anything. Two families matter after an edit. First the **surface**: the member's name and its parameter and return shapes must stay as they were, because code compiled elsewhere resolved against exactly that. Second the **body's bookkeeping**: a method declares how much working space and how many local slots it needs, which regions its exception handlers cover, and the types that reach each instruction; inserted instructions that exceed the declared working space, reuse a slot the original body still needs, or leave a differently typed value on one path produce a unit that is rejected. Where the runtime verifies units on the way in, that rejection names the unit, not the tool that produced it.

code

pseudocode · 13 lines
pseudocode
// original body — declares: working space = 2
  load id                 // holds 1
  call store.lookup       // holds 1 (the result)
  return

// after insertion — still declares: working space = 2
  call clock              // holds 1 (start)
  load id                 // holds 2
  call store.lookup       // holds 2 (start, result)
  call clock              // holds 3  <-- exceeds the declared 2
  subtract
  call record
  return

go deeper

for a junior

Remember that a compiled unit carries declarations about itself, so instructions cannot simply be pasted in without updating what the method says it needs.

for a middle

Explain the two families: the declared surface that outside callers resolved against, and the body's own counts and ranges that must match the instructions actually present.

for a senior

Anticipate where the refusal lands — on load, in another process, naming a unit nobody hand-wrote — and put a load check in the pipeline so it lands in the build instead.

for a principal

Treat structural validity as a gate the platform owns: rewritten artifacts are loaded once before release, and stale debug metadata is a declared, accepted cost rather than a surprise.

## Editing is constrained, not free A compiled unit is a structured record: a declared type, its members, and for each member a body made of instructions plus **declarations about that body**. Those declarations are not decoration — a runtime that checks units on load uses them to prove, before executing anything, that the instruction sequence cannot misbehave in specific ways. A rewriter that inserts instructions and forgets to update the declarations produces a unit that is structurally inconsistent, and the result is a refusal rather than a subtly wrong program. ## The surface must survive Everything outside the unit that already referred to this member resolved against its **declared shape**: the member's name, its parameter shapes, its return shape, and the type that declares it. - Keep the name, the parameter list and the return shape exactly as they were, or callers compiled elsewhere no longer resolve. - Do not remove members, even ones you believe are unused, and be wary of adding them: some rewriting moments permit new members and some do not. - Adding a wholly new member for your own use is safer than changing an existing one, but it widens the surface the next upstream release can collide with. The practical rule is that a rewrite of this kind changes **bodies**, and treats the declared surface as fixed. ## The body's bookkeeping must match the instructions Inside a body, several counts and ranges describe what the instructions do. Inserted code changes what the instructions do, so it must change those too. - **Working space.** In a format where instructions operate on a working stack, the method declares the maximum depth it ever reaches. Timing code that holds a start value across the original call raises that depth, and the declaration must rise with it. - **Local slots.** A value you store for later needs a slot the original body is not using; reusing an occupied one corrupts the original computation, and adding one raises the declared slot count. - **Exception ranges.** If the insertion wraps the original code so that a measurement is recorded even on failure, the protected region and its handler have to be described, and the range must cover exactly the instructions it claims to. - **Type consistency on every path.** Where two paths meet, the values in flight must agree in type. Inserting a branch that leaves one extra value on one side is the classic rejection. - **Debug metadata.** Line and local-name tables describe the original instructions. After an insertion they are stale unless updated, which is why a diagnosis taken from a rewritten unit can point at a place that looks wrong. ## Where the failure shows up | Stage | What it checks | What a bad edit looks like there | |---|---|---| | The rewriting step | usually only that the target matched | success, artifact written | | Loading the unit | structural well-formedness of the unit | refusal naming the unit and the offending member | | Running the method | nothing further about structure | never reached | This ordering is the part candidates most often get wrong. The tool that performed the edit generally does not re-prove the result; the runtime that loads it does. So the error surfaces **far from the edit**, in a different process, phrased as a complaint about a unit nobody wrote by hand. ## A worked failure Take a method that loads an identifier, calls a lookup and returns the result. It declares working space for two values. Insert a timing call at the top that produces a start value and holds it across the original call, and a second one at the bottom: at the moment of the second call the body holds the start value, the result and the new reading at once. Three values live where two were declared, and the unit is refused on load — even though every instruction in it is individually legal and the logic is exactly what was intended. ## What good practice looks like 1. Let a library compute the bookkeeping rather than hand-writing the counts; the counts are mechanical and hand-maintained ones drift. 2. Load every rewritten artifact once in a check step, so refusal happens in the build and not in production. 3. Update or drop stale debug metadata deliberately, and say which you did, so whoever reads a later diagnosis knows how much to trust the reported positions.

  • Why is a rewriting library usually preferred over splicing the instructions by hand?
    Because the bookkeeping is mechanical and unforgiving. A library recomputes working space, slot counts and exception ranges from the instructions it emitted, and can re-run the structural checks on its output. Hand-maintained counts drift the first time someone adds an instruction and forgets the declaration that describes it.
  • The rewritten artifact loads fine but a later diagnosis points at a line that does not match the source. What happened?
    The debug metadata still describes the original instruction positions while the instructions have moved, so reported positions are shifted or point inside inserted code that has no source at all. It is a stale mapping, not a wrong stack of calls, and it is a standing cost of editing bodies after compilation.

saying these in an interview costs you the question

  • Thinks the rewriting tool verifies its own output before writing it
  • Says any instruction sequence loads as long as each instruction is legal
  • Assumes inserted code can reuse local slots the original body still needs
  • Believes renaming a member is safe because callers resolve by position
  • Treats debug metadata as updating itself when instructions move