From 7d18d45f958437af1786edbeaa3c5f5e20bd55ec Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sat, 14 Nov 2026 13:48:25 +0000 Subject: [PATCH 1/2] own: a let given only fields borrows each until the next assignment (TSLANG 56) Co-Authored-By: Claude Opus 5.5 --- TSLANG_ISSUES.md | 16 +++ .../OwnershipInferenceBorrowers.cpp | 99 ++++++++++++++++++- .../lib/TypeScript/OwnershipInferencePass.h | 12 ++- .../OwnershipInferencePlaceReads.cpp | 22 ++++- tslang/test/tester/CMakeLists.txt | 6 +- ..._err_let_borrows_reassigned_overwritten.ts | 21 ++++ tslang/test/tester/tests/00tslang_issue_56.ts | 53 ++++++++++ 7 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 tslang/test/tester/own/own_err_let_borrows_reassigned_overwritten.ts create mode 100644 tslang/test/tester/tests/00tslang_issue_56.ts diff --git a/TSLANG_ISSUES.md b/TSLANG_ISSUES.md index 12f2955b1..419e61591 100644 --- a/TSLANG_ISSUES.md +++ b/TSLANG_ISSUES.md @@ -323,6 +323,22 @@ given a new array (`let dir = this.dir; ... dir = rotateAround(...)`). Regressio `tslang/test/tester/tests/00tslang_issue_55.ts` (every model, and as an exe) and `own/own_err_borrowing_array_field_reset.ts`. +56 (a `let` given only fields: `let layout = this.computeLayout; ... if (this.fp16Enabled) { layout = +this.computeLayoutFP16; ... }` in `examples/nri/16bit_arithmetic_nri.ts`, refused "borrows a field and +cannot be assigned; -mm=own cannot prove that yet") is fixed. An owning `let` whose declaration and +every assignment give it a read of a field or an element, or a parameter, borrows each value it is +given until the next assignment. Each read's uses are the `let`'s reads its own store reaches before +another assignment, and the assignments end it, so a field written once the `let` holds another field +is fine, in a loop too. rc's references for the `let` (its retain, its releases, and the retain for each +assignment) go. Still refused: the field the `let` holds written (or a call that may write it) before a +use, the `let` stored or returned, and a fresh value assigned (`dir = rotateAround(...)` in +`core/donut_first_person_camera.ts`, which would make the `let` own its value on some ways and borrow +it on others). One shape is refused that is sound: in a loop that reads the `let`, assigns it another +field and then writes the first, a read after the loop is taken to see the first field. In the sweep +the only `cannot be assigned` errors left are the camera's two. Regression tests +`tslang/test/tester/tests/00tslang_issue_56.ts` (every model, and as an exe) and +`own/own_err_let_borrows_reassigned_overwritten.ts`. + For native_ts_graphics, from the same sweep (its code, not the compiler): - `examples/dynamic_primitive_clipping.ts:223`, `desc.setDepthClip(clip)` with `clip` the loop's `int`, fails under every model since #846 (a number is not a `boolean` argument, TS2345). alpha127 still diff --git a/tslang/lib/TypeScript/OwnershipInferenceBorrowers.cpp b/tslang/lib/TypeScript/OwnershipInferenceBorrowers.cpp index 5af3de8e2..c3fc74463 100644 --- a/tslang/lib/TypeScript/OwnershipInferenceBorrowers.cpp +++ b/tslang/lib/TypeScript/OwnershipInferenceBorrowers.cpp @@ -249,12 +249,107 @@ namespace own_inference return true; } - mlir::Operation *OwnershipInferencePass::collectBorrowerUses(mlir_ts::VariableOp borrower, llvm::ArrayRef kills, + bool OwnershipInferencePass::borrowsOnly(mlir_ts::VariableOp varOp) + { + // `let layout = this.computeLayout; if (fp16) layout = this.computeLayoutFP16;` (TSLANG 56): + // an owning `let` given only reads of places and parameters, each a borrow of its own + auto borrowed = [&](mlir::Value value) { + auto root = rootOf(value); + auto rootLoad = root.getDefiningOp(); + auto slot = rootLoad ? rootLoad.getReference().getDefiningOp() : mlir_ts::VariableOp(); + int index = -1; + return placeReadOf(root) || (slot && isParameterSlot(slot, index)); + }; + + if (!varOp.getInitializer() || !isOwningVariable(varOp) || varOp.getCaptured().value_or(false) || + !borrowed(varOp.getInitializer())) + { + return false; + } + + auto assigned = false; + for (auto *user : varOp.getResult().getUsers()) + { + if (isAssignmentOf(user, varOp)) + { + if (!borrowed(mlir::cast(user).getValue())) + { + return false; + } + + assigned = true; + } + } + + return assigned; + } + + mlir_ts::VariableOp OwnershipInferencePass::borrowingAssignmentOf(mlir::Operation *user, mlir::Value value) + { + // the assignment itself + if (auto storeOp = mlir::dyn_cast(user); storeOp && storeOp.getValue() == value) + { + auto varOp = storeOp.getReference().getDefiningOp(); + return varOp && borrowsOnly(varOp) ? varOp : mlir_ts::VariableOp(); + } + + // rc's reference for it, right before it + if (mlir::isa(user) && user->getOperand(0) == value) + { + for (auto *next = user->getNextNode(); next; next = next->getNextNode()) + { + if (auto storeOp = mlir::dyn_cast(next); storeOp && storeOp.getValue() == value) + { + auto varOp = storeOp.getReference().getDefiningOp(); + return varOp && borrowsOnly(varOp) ? varOp : mlir_ts::VariableOp(); + } + + if (!mlir::isa(next)) + { + break; + } + } + } + + return {}; + } + + mlir::Operation *OwnershipInferencePass::collectBorrowerUses(mlir_ts::VariableOp borrower, llvm::ArrayRef givenKills, llvm::SmallVectorImpl &uses, - llvm::SmallVectorImpl &bookkeeping) + llvm::SmallVectorImpl &bookkeeping, mlir::Operation *from) { + // a `let` given only borrows (TSLANG 56): each borrow lasts until the next assignment, and its + // reads are those its own store (`from`: the declaration or an assignment) reaches before one + llvm::SmallVector kills(givenKills.begin(), givenKills.end()); + llvm::SmallVector assignments; + auto onlyBorrows = borrowsOnly(borrower); + if (onlyBorrows) + { + for (auto *user : borrower.getResult().getUsers()) + { + if (isAssignmentOf(user, borrower)) + { + kills.push_back(user); + if (user != from) + { + assignments.push_back(user); + } + } + } + } + for (auto *user : borrower.getResult().getUsers()) { + if (onlyBorrows && isAssignmentOf(user, borrower)) + { + continue; + } + + if (onlyBorrows && from && mlir::isa(user) && !reachableAfter(from, user, assignments)) + { + continue; + } + if (mlir::isa(user)) { bookkeeping.push_back(user); diff --git a/tslang/lib/TypeScript/OwnershipInferencePass.h b/tslang/lib/TypeScript/OwnershipInferencePass.h index 8a44a1cee..31d52ec06 100644 --- a/tslang/lib/TypeScript/OwnershipInferencePass.h +++ b/tslang/lib/TypeScript/OwnershipInferencePass.h @@ -292,6 +292,12 @@ class OwnershipInferencePass // An array literal held in a local, made only of reads of places and parameters and only read // (TSLANG 55): it borrows its elements while it is used, and its releases are shallow. void classifyLocalBorrowingArrays(); + // An owning `let` given only reads of places and parameters, and assigned at least once + // (TSLANG 56): each value it is given is a borrow until the next assignment. + bool borrowsOnly(mlir_ts::VariableOp varOp); + // The `let` (borrowsOnly) that `user` assigns `value` to - the store itself, or rc's retain right + // before it - or null. + mlir_ts::VariableOp borrowingAssignmentOf(mlir::Operation *user, mlir::Value value); // Is `ref` a cell read out of the `.captured` box of a generator's state object that borrows: // `ts.Load(ts.PropertyRef(ts.Load(ts.PropertyRef(state, captured)), i))`? @@ -517,10 +523,12 @@ class OwnershipInferencePass // A borrowing `let`'s slot: each read, and everything it leads to (walkBorrowed), joins // `uses`, made again at `kills`; its `ts.RetainSlot` and `ts.ReleaseSlot`s are `bookkeeping`, // which a borrow erases. Answers the first use that would keep what the slot holds - an - // assignment, or anything else that is not a read - or null. + // assignment, or anything else that is not a read - or null. For a `let` given only borrows + // (borrowsOnly, TSLANG 56) its assignments end each borrow, and with `from` (the store of the + // value: the declaration or an assignment) only the reads it reaches before one are its uses. mlir::Operation *collectBorrowerUses(mlir_ts::VariableOp borrower, llvm::ArrayRef kills, llvm::SmallVectorImpl &uses, - llvm::SmallVectorImpl &bookkeeping); + llvm::SmallVectorImpl &bookkeeping, mlir::Operation *from = nullptr); static bool isAssignmentOf(mlir::Operation *op, mlir_ts::VariableOp varOp); diff --git a/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp b/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp index 489360adb..0f98cc3b9 100644 --- a/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp +++ b/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp @@ -833,11 +833,29 @@ namespace own_inference auto borrower = borrowerOf(user, /*consumed=*/true); if (!borrower || borrower.getOperation() != user) { - return false; + // assigned to a `let` given only borrows (TSLANG 56): the `let` borrows it from the + // assignment on, and rc's retain for the assignment goes + auto storeOp = mlir::dyn_cast(user); + auto operand = storeOp ? storeOp.getValue() + : (mlir::isa(user) ? user->getOperand(0) : mlir::Value()); + auto assigned = operand && rootOf(operand) == rootOf(result) ? borrowingAssignmentOf(user, operand) + : mlir_ts::VariableOp(); + if (!assigned) + { + return false; + } + + if (!storeOp) + { + bookkeeping.push_back(user); + return true; + } + + borrower = assigned; } name = varName(borrower); - if (auto *keeps = collectBorrowerUses(borrower, kills, uses, bookkeeping); keeps && !letKept) + if (auto *keeps = collectBorrowerUses(borrower, kills, uses, bookkeeping, user); keeps && !letKept) { letKept = keeps; letKeeps = borrower; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index d226baf95..6f44f1ae9 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -1909,9 +1909,10 @@ endforeach() # TSLANG_ISSUES 53: a value a constructor keeps in a field, used after, is a borrow of that field # TSLANG_ISSUES 54: a `?:` of fresh values or null is the function's own # TSLANG_ISSUES 55: an array literal of fields, only read, borrows them +# TSLANG_ISSUES 56: a `let` given only fields borrows each until the next assignment foreach(issues_mm gc rc none own) foreach(issues_test 00tslang_issue_44 00tslang_issue_45 00tslang_issue_46 00tslang_issue_47 00tslang_issue_48 00tslang_issue_49 00tslang_issue_50 - 00tslang_issue_52 00tslang_issue_53 00tslang_issue_54 00tslang_issue_55) + 00tslang_issue_52 00tslang_issue_53 00tslang_issue_54 00tslang_issue_55 00tslang_issue_56) tslang_add_test(NAME test-jit-${issues_test}-${issues_mm} COMMAND test-runner -jit -mm=${issues_mm} "${PROJECT_SOURCE_DIR}/test/tester/tests/${issues_test}.ts") tslang_add_test(NAME test-compile-${issues_test}-${issues_mm} COMMAND test-runner -mm=${issues_mm} "${PROJECT_SOURCE_DIR}/test/tester/tests/${issues_test}.ts") endforeach() @@ -5459,7 +5460,8 @@ set(own_error_cases "own_err_pushed_then_popped|:14:16: error: '[^']*' borrows an element but is used here after it may be released or overwritten" "own_err_kept_by_constructor_reset|:16:11: error: '[^']*' borrows a field but is used here after it may be released or overwritten" "own_err_merge_moved_twice|:19:9: error: '[^']*' borrows a field and cannot be stored, returned or captured" - "own_err_borrowing_array_field_reset|:13:16: error: '[^']*' borrows a field but is used here after it may be released or overwritten") + "own_err_borrowing_array_field_reset|:13:16: error: '[^']*' borrows a field but is used here after it may be released or overwritten" + "own_err_let_borrows_reassigned_overwritten|:17:16: error: '[^']*' borrows a field but is used here after it may be released or overwritten") foreach(own_error_case ${own_error_cases}) string(FIND "${own_error_case}" "|" own_error_sep) string(SUBSTRING "${own_error_case}" 0 ${own_error_sep} own_error_name) diff --git a/tslang/test/tester/own/own_err_let_borrows_reassigned_overwritten.ts b/tslang/test/tester/own/own_err_let_borrows_reassigned_overwritten.ts new file mode 100644 index 000000000..addcb5826 --- /dev/null +++ b/tslang/test/tester/own/own_err_let_borrows_reassigned_overwritten.ts @@ -0,0 +1,21 @@ +// TSLANG_ISSUES 56: a `let` given only fields borrows each until the next assignment - but the field +// it holds now, written before the use, frees what it holds, so the use is refused. +class Pipe { + id: int = 3; +} + +class K { + a: Pipe = new Pipe(); + b: Pipe = new Pipe(); + f = true; + run(): int { + let l = this.a; + if (this.f) { + l = this.b; + } + this.b = new Pipe(); + return l.id; + } +} + +function main() { print(new K().run()); } diff --git a/tslang/test/tester/tests/00tslang_issue_56.ts b/tslang/test/tester/tests/00tslang_issue_56.ts new file mode 100644 index 000000000..0c882cf47 --- /dev/null +++ b/tslang/test/tester/tests/00tslang_issue_56.ts @@ -0,0 +1,53 @@ +// TSLANG_ISSUES 56 (-mm=own), from native_ts_graphics' examples/nri/16bit_arithmetic_nri.ts: a `let` +// given only fields (or parameters) - `let layout = this.computeLayout; if (this.fp16Enabled) layout = +// this.computeLayoutFP16;` - borrows each until the next assignment. A field written once the `let` +// holds another is fine. + +class Pipe { + id: int = 3; +} + +class K { + a: Pipe = new Pipe(); + b: Pipe = new Pipe(); + fp16 = false; + + pick(): int { + let layout = this.a; + let other = this.b; + if (this.fp16) { + layout = this.b; + other = this.a; + } + + return layout.id * 10 + other.id; + } + + // `a` written after `l` moved on to `b`, in a loop too + moveOn(p: Pipe): int { + let l = this.a; + l = this.b; + this.a = new Pipe(); + let total = l.id; + let m = p; + for (let i = 0; i < 2; i++) { + total += m.id; + this.a = new Pipe(); + m = this.b; + } + + return total; + } +} + +function main() { + const k = new K(); + k.b.id = 5; + assert(k.pick() == 35); + k.fp16 = true; + assert(k.pick() == 53); + const n = k.moveOn(new Pipe()); + print(n); + assert(n == 5 + 3 + 5); + print("done."); +} From e44a325250c01de0b00ff391c4209f3ff3610ebb Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sat, 14 Nov 2026 14:16:46 +0000 Subject: [PATCH 2/2] own: a let that only holds parameters owns nothing (TSLANG 57) Co-Authored-By: Claude Opus 5.5 --- TSLANG_ISSUES.md | 18 +++ .../OwnershipSignatureFieldLocals.cpp | 111 ++++++++++++++++++ .../lib/TypeScript/OwnershipSignaturePass.cpp | 1 + .../lib/TypeScript/OwnershipSignaturePass.h | 4 + tslang/test/tester/CMakeLists.txt | 6 +- .../own/own_err_parameter_let_stored.ts | 14 +++ tslang/test/tester/tests/00tslang_issue_57.ts | 75 ++++++++++++ 7 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 tslang/test/tester/own/own_err_parameter_let_stored.ts create mode 100644 tslang/test/tester/tests/00tslang_issue_57.ts diff --git a/TSLANG_ISSUES.md b/TSLANG_ISSUES.md index 419e61591..0e42c79ca 100644 --- a/TSLANG_ISSUES.md +++ b/TSLANG_ISSUES.md @@ -339,7 +339,25 @@ the only `cannot be assigned` errors left are the camera's two. Regression tests `tslang/test/tester/tests/00tslang_issue_56.ts` (every model, and as an exe) and `own/own_err_let_borrows_reassigned_overwritten.ts`. +57 (a `let` that only holds parameters: `let src = order; let dst = scratch; ... const swap = src; src = +dst; dst = swap;` in `core/octomap_common.ts`'s exported `sortDescending`, refused "a parameter, kept +here, but -mm=own cannot make the callers of 'sortDescending' give it up") is fixed. Even `let s = a; +return s.length;` was refused: MLIRGen makes every `let` an owning local, so storing a parameter into +one kept the parameter. A `let` whose declaration and every assignment give it a parameter (read out of +a slot nothing assigns) or the value of another such `let` holds the caller's values for the whole +call, so before the facts the signature pass makes it a plain local that owns nothing and drops rc's +counting for it. What is done with its reads is then what is done with the parameter: stored into a +field or a global, captured, or given a fresh value (`s = [9]`) it is still refused. A `Shared` +handle (counted under own) and a string (copied) are left as they were. `octomap_common.ts` compiles under +own now, and with it `examples/render_octomap.ts`. Six of ntg's `core/` modules are left that do not. +Regression tests `tslang/test/tester/tests/00tslang_issue_57.ts` (every model, and as an exe) and +`own/own_err_parameter_let_stored.ts`. + For native_ts_graphics, from the same sweep (its code, not the compiler): +- `core/ray_tracing_extended_scene.ts`: the exported classes' constructors keep their array parameters + (`this.origin = origin`), which an exported constructor has to say: `@keeps("origin", "direction")` + on `FlameParticleGenerator`'s and `@keeps("vertices", "triangles")` on `Model`'s. `@keeps` on a + constructor works (checked). Its `this.particles.filter(...)` needs `Shared` elements. - `examples/dynamic_primitive_clipping.ts:223`, `desc.setDepthClip(clip)` with `clip` the loop's `int`, fails under every model since #846 (a number is not a `boolean` argument, TS2345). alpha127 still takes it; the next release won't. `desc.setDepthClip(clip != 0)` fixes it. diff --git a/tslang/lib/TypeScript/OwnershipSignatureFieldLocals.cpp b/tslang/lib/TypeScript/OwnershipSignatureFieldLocals.cpp index 23683e810..472f6d7a8 100644 --- a/tslang/lib/TypeScript/OwnershipSignatureFieldLocals.cpp +++ b/tslang/lib/TypeScript/OwnershipSignatureFieldLocals.cpp @@ -383,6 +383,117 @@ namespace own_signature }; } // namespace + void OwnershipSignaturePass::borrowParametersInLocals(mlir::ModuleOp module) + { + // `let src = order; let dst = scratch; ... const swap = src; src = dst; dst = swap;` (TSLANG + // 57, native_ts_graphics' sortDescending): a `let` that only ever holds the function's + // parameters - read out of their slots, or out of another such `let` - holds the caller's + // values for the whole call. MLIRGen made it an owning local, so storing a parameter into it + // kept the parameter. It owns nothing: it is made a plain local, its rc counting goes, and what + // is done with its reads is what is done with the parameter. + module.walk([&](mlir::FunctionOpInterface funcOp) { + if (funcOp.isExternal()) + { + return; + } + + auto &body = funcOp.getFunctionBody(); + llvm::SetVector aliases; + body.walk([&](mlir_ts::VariableOp varOp) { + // not a Shared handle, which own counts (spec 23.2), nor a string and the like, + // which a local takes a copy of + auto type = varOp.getInitializer() ? varOp.getInitializer().getType() : mlir::Type(); + if (varOp->hasAttr(OWNED_LOCAL_ATTR_NAME) && !varOp->hasAttr(OWNED_LOCAL_CONSUMED_ATTR_NAME) && + type && !varOp.getCaptured().value_or(false) && !MLIRTypeHelper::isSharedHandleType(type) && + !own_facts::isCopyableValueAt(varOp, type)) + { + aliases.insert(varOp); + } + }); + + // a parameter's slot nothing assigns, or a `let` still in the set + auto aliased = [&](mlir::Value value) { + auto loadOp = own_facts::rootOf(value).getDefiningOp(); + auto slot = loadOp ? loadOp.getReference().getDefiningOp() : mlir_ts::VariableOp(); + int index = -1; + return slot && (aliases.contains(slot) || (own_facts::isParameterSlot(slot, index) && isUnassignedSlot(slot.getResult()))); + }; + + auto holdsOnlyParameters = [&](mlir_ts::VariableOp varOp) { + if (!aliased(varOp.getInitializer())) + { + return false; + } + + for (auto *user : varOp->getUsers()) + { + if (mlir::isa(user)) + { + continue; + } + + auto storeOp = mlir::dyn_cast(user); + if (!storeOp || storeOp.getReference() != varOp.getResult() || !aliased(storeOp.getValue())) + { + return false; + } + } + + return true; + }; + + // to a fixed point: a `let` that takes another's value stays only while that one does + for (auto changed = true; changed;) + { + changed = false; + for (auto varOp : llvm::to_vector(aliases)) + { + if (!holdsOnlyParameters(varOp)) + { + aliases.remove(varOp); + changed = true; + } + } + } + + for (auto varOp : aliases) + { + varOp->removeAttr(OWNED_LOCAL_ATTR_NAME); + llvm::SmallVector counting; + for (auto *user : varOp->getUsers()) + { + if (mlir::isa(user)) + { + counting.push_back(user); + } + + // rc's retain for an assignment, right before it (`Retain(v); ReleaseSlot(x); Store(v, x)`) + if (auto storeOp = mlir::dyn_cast(user)) + { + for (auto *before = user->getPrevNode(); before; before = before->getPrevNode()) + { + if (auto retainOp = mlir::dyn_cast(before); retainOp && retainOp->getOperand(0) == storeOp.getValue()) + { + counting.push_back(before); + break; + } + + if (!mlir::isa(before)) + { + break; + } + } + } + } + + for (auto *op : counting) + { + op->erase(); + } + } + }); + } + void OwnershipSignaturePass::readFieldsForLocals(mlir::ModuleOp module) { llvm::SmallVector bodies; diff --git a/tslang/lib/TypeScript/OwnershipSignaturePass.cpp b/tslang/lib/TypeScript/OwnershipSignaturePass.cpp index 12ce9eb4e..a84a5c11e 100644 --- a/tslang/lib/TypeScript/OwnershipSignaturePass.cpp +++ b/tslang/lib/TypeScript/OwnershipSignaturePass.cpp @@ -102,6 +102,7 @@ namespace own_signature { auto module = getOperation(); stubCannotShare(module); + borrowParametersInLocals(module); readFieldsForLocals(module); module.walk([&](mlir_ts::FuncOp funcOp) { functions[funcOp.getSymName()] = funcOp; }); module.walk([&](mlir_ts::GlobalOp globalOp) { globals[globalOp.getSymName()] = globalOp; }); diff --git a/tslang/lib/TypeScript/OwnershipSignaturePass.h b/tslang/lib/TypeScript/OwnershipSignaturePass.h index 1172dfc79..86d1827b6 100644 --- a/tslang/lib/TypeScript/OwnershipSignaturePass.h +++ b/tslang/lib/TypeScript/OwnershipSignaturePass.h @@ -1133,6 +1133,10 @@ class OwnershipSignaturePass : public mlir::PassWrapper key[src[i]]) { + dst[k] = src[j]; + j++; + } else { + dst[k] = src[i]; + i++; + } + + k++; + } + + while (i < mid) { + dst[k] = src[i]; + i++; + k++; + } + + while (j < right) { + dst[k] = src[j]; + j++; + k++; + } + } + + const swap = src; + src = dst; + dst = swap; + inScratch = !inScratch; + width *= 2; + } + + if (inScratch) { + for (let i = 0; i < n; i++) { + order[i] = src[i]; + } + } +} + +export function longer(a: int[], b: int[]): int { + let s = a; + if (b.length > a.length) { + s = b; + } + + return s.length; +} + +function main() { + const order = [0, 1, 2, 3, 4]; + const scratch = [0, 0, 0, 0, 0]; + sortDescending(order, [5.0, 1.0, 9.0, 3.0, 7.0], scratch); + print(order[0], order[1], order[2], order[3], order[4]); + assert(order[0] == 2 && order[1] == 4 && order[2] == 0 && order[3] == 3 && order[4] == 1); + assert(longer([1], [1, 2, 3]) == 3 && longer([1, 2], [1]) == 2); + print("done."); +}