Skip to content

Commit 3d2ef2e

Browse files
committed
feat(regions): retargeting of returned literals into argument region
1 parent 5b73ca2 commit 3d2ef2e

4 files changed

Lines changed: 130 additions & 21 deletions

File tree

docs/overview/08-memory-management.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ struct Point:
106106
end
107107
108108
func make_point(x, y, r: Region) -> Point:
109-
return Point{x = x, y = y}[r] // OK: allocated caller-provided region
109+
return Point{x = x, y = y}[r] // OK: allocated in caller-provided region
110110
end
111111
112112
func bad_point(x, y) -> Point:
@@ -397,7 +397,39 @@ func main():
397397
end
398398
```
399399

400-
#### 8.5.3 Nested composite literals
400+
#### 8.5.3 Returned literal retargeting
401+
402+
For a composite-returning function, the same-region rule determines a shared
403+
caller-owned result region from its non-`foreign` composite parameters and
404+
`Region` parameters. A composite literal used directly as a return expression
405+
is automatically allocated in that region:
406+
407+
```jik
408+
func display_name(user: Person) -> String:
409+
if user.name == "":
410+
return "Anonymous" // allocated in user's region
411+
end
412+
return user.name
413+
end
414+
415+
func make_point(x, y, r: Region) -> Point:
416+
return Point{x = x, y = y} // allocated in r
417+
end
418+
```
419+
420+
This rule applies only to a directly returned composite literal. It does not
421+
retarget a local variable, a literal with an explicit allocation specifier, a
422+
`foreign` value, or a global value. Without an eligible parameter, returning a
423+
local literal remains an error. Explicit allocation remains valid when it
424+
improves clarity:
425+
426+
```jik
427+
func make_point_explicit(x, y, r: Region) -> Point:
428+
return Point{x = x, y = y}[r]
429+
end
430+
```
431+
432+
#### 8.5.4 Nested composite literals
401433

402434
Composite literals that contain other composite values must be internally region-consistent.
403435
The outer value and the contained composite values must belong to the same region.
@@ -426,7 +458,7 @@ func foo(v: Vec[String]):
426458
end
427459
```
428460

429-
#### 8.5.4 Temporary containers passed to `foreign` parameters
461+
#### 8.5.5 Temporary containers passed to `foreign` parameters
430462

431463
A temporary `Vec` or `Dict` literal passed directly to a `foreign` parameter
432464
may contain composite elements from different regions. This exception applies
@@ -459,7 +491,7 @@ Since `args` in `process::capture` is a `foreign` vector, at the call site the e
459491
need not be in the same region.
460492

461493

462-
#### 8.5.5 Region-safe builtins
494+
#### 8.5.6 Region-safe builtins
463495

464496
All currently provided builtins except `push` are region-safe, so their calls
465497
do not need to satisfy the same-region rule. Some only inspect their composite

examples/README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ standard library.
88
1. `hello.jik` - the smallest complete Jik program
99
2. `fib.jik` - functions, loops, recursion, and region-based allocation
1010
3. `regions_copy.jik` - returning copied composite values in a caller-chosen region
11-
4. `primes.jik` - loops and vectors
12-
5. `word_count.jik` - structs, file I/O, and standard library use
13-
6. `text_processing.jik` - string/vector slices, indexed iteration, and comparisons
14-
7. `modules/main.jik` - multi-file programs, modules, and imports
15-
8. `enum_match.jik` - exhaustive matching over enum values
16-
9. `variants.jik` - enums, payload-less variants, `match`, and UFCS
17-
10. `tables.jik` - exhaustive enum lookup tables and table-driven transitions
18-
11. `error_handling.jik` - `throws`, recovery, propagation, `must`, and postfix `!`
19-
12. `ffi_demo.jik` - calling C functions and opaque C structs through Jik's FFI
20-
13. `testing_demo.jik` - basic use of `jik/testing`
21-
14. `cl_args.jik` - raw command-line argument handling
22-
15. `argparse_demo.jik` - parsed arguments, generated help, and path normalization
23-
16. `process_capture.jik` - capture a process and inspect stdout/stderr
11+
4. `region_ergonomics.jik` - inferred regions for returned, nested, and stored literals
12+
5. `primes.jik` - loops and vectors
13+
6. `word_count.jik` - structs, file I/O, and standard library use
14+
7. `text_processing.jik` - string/vector slices, indexed iteration, and comparisons
15+
8. `modules/main.jik` - multi-file programs, modules, and imports
16+
9. `enum_match.jik` - exhaustive matching over enum values
17+
10. `variants.jik` - enums, payload-less variants, `match`, and UFCS
18+
11. `tables.jik` - exhaustive enum lookup tables and table-driven transitions
19+
12. `error_handling.jik` - `throws`, recovery, propagation, `must`, and postfix `!`
20+
13. `ffi_demo.jik` - calling C functions and opaque C structs through Jik's FFI
21+
14. `testing_demo.jik` - basic use of `jik/testing`
22+
15. `cl_args.jik` - raw command-line argument handling
23+
16. `argparse_demo.jik` - parsed arguments, generated help, and path normalization
24+
17. `process_capture.jik` - capture a process and inspect stdout/stderr
2425

2526
The remaining examples are larger demonstrations:
2627

examples/region_ergonomics.jik

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Example: region ergonomics in Jik.
2+
3+
struct User:
4+
name: String
5+
labels: Vec[String]
6+
end
7+
8+
9+
func new_user(foreign name: String, r: Region) -> User:
10+
// The returned struct and its composite field `labels` are automatically
11+
// allocated in r, since this is the only possible valid allocation destination.
12+
// Since `name` is marked as a foreign parameter, we still need to copy it to `r`.
13+
return User{
14+
name = copy(name, r),
15+
labels = ["new", "active"]
16+
}
17+
end
18+
19+
20+
func display_name(user: User) -> String:
21+
if user.name == "":
22+
// The returned string literal is allocated in user's region, since
23+
// this is the only valid destination.
24+
return "Anonymous"
25+
end
26+
return user.name
27+
end
28+
29+
30+
func add_default_label(user: User):
31+
// The string literal is automatically allocated in user's region.
32+
// This is also valid for other store operations involving composite values.
33+
push(user.labels, "member")
34+
end
35+
36+
37+
func main():
38+
// An omitted final Region argument automatically passes the current
39+
// function's local region `_`.
40+
user := new_user("Ada")
41+
42+
add_default_label(user)
43+
println(display_name(user), ": ", user.labels)
44+
end

src/jik/regcheck.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,29 @@ jik_alloc_spec_complete(JikAllocSpec s)
622622
return s.kind != JIK_ALLOC_UNKNOWN && s.src != JIK_ALLOC_SRC_UNKNOWN;
623623
}
624624

625+
static JikAllocSpec *
626+
get_function_implicit_region_spec(JikNode *func_nd, TabJikAllocSpec *spec_tab)
627+
{
628+
assert(func_nd->type == NODE_FUNCTION);
629+
if (!jik_type_is_allocated(func_nd->jik_type->val_func.ret_type)) {
630+
return NULL;
631+
}
632+
633+
for (size_t i = 0; i < VecJikNode_size(func_nd->val_function.params); i++) {
634+
JikNode *param = VecJikNode_get(func_nd->val_function.params, i);
635+
if (param->jik_type != &JIK_TYPE_REGION &&
636+
(!jik_type_is_allocated(param->jik_type) || param->val_id.is_foreign)) {
637+
continue;
638+
}
639+
640+
JikAllocSpec *spec = TabJikAllocSpec_get(spec_tab, param->val_id.name);
641+
if (spec && jik_alloc_spec_complete(*spec)) {
642+
return spec;
643+
}
644+
}
645+
return NULL;
646+
}
647+
625648
static bool
626649
jik_alloc_sources_match(JikAllocSpec s1, JikAllocSpec s2)
627650
{
@@ -1021,18 +1044,27 @@ jik_check_region_integrity(JikNode *ast)
10211044
if (!jik_type_is_allocated(nd->val_return.expr->jik_type)) {
10221045
continue;
10231046
}
1024-
JikAllocSpec spec = get_expression_alloc_spec(nd->val_return.expr, spec_tab);
1047+
JikNode *expr = nd->val_return.expr;
1048+
JikAllocSpec spec = get_expression_alloc_spec(expr, spec_tab);
1049+
if (jik_node_is_allocated_literal(expr) && spec.src == JIK_ALLOC_SRC_LOCAL) {
1050+
JikAllocSpec *implicit_spec =
1051+
get_function_implicit_region_spec(func_nd, spec_tab);
1052+
if (implicit_spec && can_retarget_literal(expr, *implicit_spec)) {
1053+
jik_set_alloc_spec(expr, *implicit_spec);
1054+
spec = get_expression_alloc_spec(expr, spec_tab);
1055+
}
1056+
}
10251057
if (jik_alloc_source_known(spec) && spec.src == JIK_ALLOC_SRC_LOCAL) {
10261058
jik_diag_fatal_error("cannot return local allocation",
1027-
jik_token_to_text(nd->val_return.expr->token));
1059+
jik_token_to_text(expr->token));
10281060
}
10291061
else if (jik_alloc_source_known(spec) && spec.src == JIK_ALLOC_SRC_CROSS) {
10301062
jik_diag_fatal_error("cannot return foreign composite value",
1031-
jik_token_to_text(nd->val_return.expr->token));
1063+
jik_token_to_text(expr->token));
10321064
}
10331065
else if (jik_alloc_source_known(spec) && spec.kind == JIK_ALLOC_GLOBAL) {
10341066
jik_diag_fatal_error("cannot return composite global",
1035-
jik_token_to_text(nd->val_return.expr->token));
1067+
jik_token_to_text(expr->token));
10361068
}
10371069
}
10381070
else if (jik_node_is_allocated_literal(nd)) {

0 commit comments

Comments
 (0)