Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions docs/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,31 @@ i32[1] arr = {
};
```

### Zero Initialization

`zero` zero-initializes aggregates such as arrays, structures, vectors, and
matrices. `nil` is used in pointer-like contexts.

```
type Buffer = struct {
i8* ptr;
u64 length;
};

let Buffer buffer = zero;
let mut u32[16] words = zero;
pub state Buffer[4] buffers = zero;
```

Numeric elements and fields become `0` or `0.0`, pointer-like fields become
`nil`, and nested aggregates are zero-initialized recursively. `zero` is
contextually typed, so the surrounding declaration or expression must provide
the complete aggregate type. For example, `let value = zero;` is invalid.

`zero` is not a partial aggregate initializer. A brace initializer must still
provide every required element or field. Scalar and enum targets are rejected;
use an ordinary numeric literal or `nil` when initializing those values.

### Boxed Types

> [!CAUTION]
Expand Down Expand Up @@ -392,9 +417,17 @@ state i32 x = 1234; // mutable, local
pub state i32 y = 5678; // mutable, global linkage
```

For `pub` data and state, an initializer may be ommitted to create a reference to be resolved by the linker.
For `pub` data and state, an initializer may be omitted to create a reference to be resolved by the linker.
Non-constant global initializers are lowered to program startup initialization before user code runs.

An explicit `zero` initializer creates a definition rather than an external
reference. For example:

```
pub state i32[4] supplied_elsewhere; // external declaration
pub state i32[4] owned_here = zero; // zero-filled definition
```

#### Function Scope

Inside function definitions, variable declarations take a different form:
Expand Down Expand Up @@ -686,7 +719,8 @@ let node tail = { 1, nil };
let node head = { 0, ref tail };
```

`nil` may be used in lieu of a reference to indicate `NULL`.
`nil` may be used in lieu of a reference to indicate `NULL`. To zero-initialize
an aggregate, use [`zero`](#zero-initialization) instead.

### Load

Expand Down
2 changes: 1 addition & 1 deletion src/bin/lsp/inlay_hints.ml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ let rec walk_expression typing type_env query_range acc (expr : Core.expression)
walk_expression typing type_env query_range acc unary.value.inner
| Block block ->
walk_block typing type_env query_range acc block
| Literal _ | Identifier _ | Nil ->
| Literal _ | Identifier _ | Nil | Zero ->
acc
| ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner ->
walk_expression typing type_env query_range acc inner
Expand Down
4 changes: 2 additions & 2 deletions src/bin/lsp/symbol_resolution.ml
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ and walk_expression state env (expr : Core.expression) =
match literal.value with
| Core.Enum enum_lit -> enum_literal_hover state expr enum_lit
| _ -> ())
| Nil -> ()
| Nil | Zero -> ()
| ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner ->
walk_expression state env inner
| BoxConstruct box ->
Expand Down Expand Up @@ -774,7 +774,7 @@ let rec highlight_expression state env (expr : Core.expression) =
Analysis.lookup_enum_variant state.type_env expr.loc resolved
enum_lit.value.enum_variant.value))
| _ -> ())
| Nil -> ()
| Nil | Zero -> ()
| ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner ->
highlight_expression state env inner
| BoxConstruct box ->
Expand Down
3 changes: 2 additions & 1 deletion src/lib/ast/analysis_asserts.ml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ module Assert = struct
(render_expression write.value.value)
| Core.SizeType _ -> "size<type>"
| Core.Nil -> "nil"
| Core.Zero -> "zero"
in
if self_prec < ctx_prec then "(" ^ rendered ^ ")" else rendered

Expand Down Expand Up @@ -207,7 +208,7 @@ module Assert = struct
(fun expr -> render_specialized_expression state expr)
box.value.args))
| (Core.Literal _ | Core.Identifier _ | Core.Block _ | Core.Initializer _ | Core.Match _
| Core.BoxType _ | Core.SizeType _ | Core.Nil) ->
| Core.BoxType _ | Core.SizeType _ | Core.Nil | Core.Zero) ->
render_expression ~ctx_prec expr

let assert_context (compile_assert : Core.compile_assert) suffix =
Expand Down
7 changes: 4 additions & 3 deletions src/lib/ast/analysis_cfold.ml
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,14 @@ module ConstantFold = struct
| Core.Assign write -> Core.Assign (fold_write write)
| Core.Mutate write -> Core.Mutate (fold_write write)
| Core.Literal literal -> Core.Literal (fold_literal literal)
| (Core.Identifier _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as value ->
| (Core.Identifier _ | Core.SizeType _ | Core.Nil | Core.Zero | Core.BoxType _) as
value ->
value
in
let expr = { expr with value } in
match expr.value with
| Core.Literal _ | Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _
| Core.BoxConstruct _ ->
| Core.Literal _ | Core.Identifier _ | Core.Nil | Core.Zero | Core.SizeType _
| Core.BoxType _ | Core.BoxConstruct _ ->
expr
| Core.Block block when block.value.statements = [] -> (
match block.value.result with Some result -> result | None -> expr)
Expand Down
4 changes: 2 additions & 2 deletions src/lib/ast/analysis_cleanup.ml
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ module Cleanup = struct
}
| Core.Assign write -> clean_write_like typed (fun write -> Core.Assign write) write
| Core.Mutate write -> clean_write_like typed (fun write -> Core.Mutate write) write
| (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as
value ->
| (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.Zero
| Core.BoxType _) as value ->
value
in
{ expr with value = cleaned_value }
Expand Down
4 changes: 2 additions & 2 deletions src/lib/ast/analysis_ownership.ml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ module Ownership = struct
if not (resolved_contains_ownership state expr.loc expected) then ()
else
match expr.value with
| Core.Nil -> ()
| Core.Nil | Core.Zero -> ()
| Core.BoxExpr _ | Core.BoxType _ | Core.BoxConstruct _ -> ()
| Core.Call call -> emit_retains_for_expected_enum_call state reason expected call
| Core.As cast -> emit_retains_for_transfer state reason expected cast.value.inner
Expand Down Expand Up @@ -263,7 +263,7 @@ module Ownership = struct

let rec visit_expression state scopes (expr : Core.expression) =
match expr.value with
| Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _ -> ()
| Core.Identifier _ | Core.Nil | Core.Zero | Core.SizeType _ | Core.BoxType _ -> ()
| Core.BoxConstruct box ->
List.iter (visit_expression state scopes) box.value.args
| Core.Literal literal -> (
Expand Down
4 changes: 3 additions & 1 deletion src/lib/ast/analysis_purity.ml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ module Purity = struct
let rec visit_expression state current env (expr : Core.expression) =
let visit = visit_expression state current in
match expr.value with
| Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.BoxType _ | Core.Nil -> ()
| Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.BoxType _ | Core.Nil
| Core.Zero ->
()
| Core.BoxConstruct box ->
List.iter (visit env) box.value.args
| Core.ToBool inner
Expand Down
2 changes: 1 addition & 1 deletion src/lib/ast/analysis_semantic.ml
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ module Semantic = struct
| _ -> ()
in
match expr.value with
| Core.Identifier _ | Core.Literal _ | Core.Nil | Core.SizeType _ -> ()
| Core.Identifier _ | Core.Literal _ | Core.Nil | Core.Zero | Core.SizeType _ -> ()
| Core.ToBool inner ->
check_expression state env loop_depth inner;
check_scalar_truthy inner
Expand Down
3 changes: 2 additions & 1 deletion src/lib/ast/analysis_specialize.ml
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ module Specialize = struct
};
};
}
| (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.BoxType _) ->
| (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.Zero
| Core.BoxType _) ->
expr

and rewrite_call state annotations (expr : Core.expression) (call : Core.call) =
Expand Down
14 changes: 14 additions & 0 deletions src/lib/ast/analysis_typing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ module Typing = struct
integer = None;
};
}
| Core.Zero -> infer_zero state ~expected_type expr.loc
| Core.Match match_expr -> infer_match state env ~expected_type expr.loc match_expr
| Core.BoxExpr inner -> (
let inner_expected =
Expand Down Expand Up @@ -866,6 +867,19 @@ module Typing = struct
| Some expected -> annotation_of_resolved loc (Some expected)
| None -> unknown_expr_annotation

and infer_zero state ~(expected_type : resolved_ty option) loc : expr_annotation =
let valid =
match expected_type with
| Some (ResolvedArray _ | ResolvedVec _ | ResolvedMatrix _) -> true
| Some (ResolvedNamed _ as struct_ty) ->
Option.is_some (lookup_struct_fields state.type_env loc struct_ty)
| Some _ | None -> false
in
if not valid then
add_diagnostic state Error loc
"zero requires an explicit array, struct, vector, or matrix target type";
annotation_of_resolved loc expected_type

and infer_unary state env loc (unary : Core.unary) : expr_annotation =
let inner_ann = infer_value_expression state env unary.value.inner in
match unary.value.op with
Expand Down
2 changes: 1 addition & 1 deletion src/lib/ast/analysis_verify.ml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module Verify = struct
and verify_expression state (expr : Core.expression) =
let verify_subexprs () =
match expr.value with
| Core.Identifier _ | Core.Literal _ | Core.Nil -> ()
| Core.Identifier _ | Core.Literal _ | Core.Nil | Core.Zero -> ()
| Core.ToBool inner
| Core.SizeExpr inner
| Core.BoxExpr inner
Expand Down
1 change: 1 addition & 0 deletions src/lib/ast/cimport.ml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ let is_haven_keyword =
"Mat";
"mut";
"nil";
"zero";
"pub";
"ref";
"ret";
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ast/convert.ml
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ and cst_expr_to_surface (expr : Cst.expression) : Surface.expression =
| Cst.SizeExpr inner -> Surface.SizeExpr (cst_expr_to_surface inner)
| Cst.SizeType ty -> Surface.SizeType (cst_type_to_surface ty)
| Cst.Nil -> Surface.Nil
| Cst.Zero -> Surface.Zero
| Cst.If ifx -> Surface.If (cst_if_expr_to_surface ifx)
| Cst.Match m -> Surface.Match (cst_match_expr_to_surface m)
| Cst.BoxExpr inner -> Surface.BoxExpr (cst_expr_to_surface inner)
Expand Down Expand Up @@ -1278,6 +1279,7 @@ and surface_expr_to_core st (expr : Surface.expression) : Core.expression =
| Surface.SizeExpr inner -> Core.SizeExpr (surface_expr_to_core st inner)
| Surface.SizeType ty -> Core.SizeType (surface_type_to_core ty)
| Surface.Nil -> Core.Nil
| Surface.Zero -> Core.Zero
| Surface.If ifx -> lower_if_expr st expr.loc ifx
| Surface.Match m ->
Core.Match
Expand Down
1 change: 1 addition & 0 deletions src/lib/ast/core_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ and expression_desc =
| SizeExpr of expression
| SizeType of haven_type
| Nil
| Zero
| Match of match_expr
| BoxExpr of expression
| BoxType of haven_type
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ast/llvm_ir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ let rec constant_of_expr t (expr : Core.expression) =
| Core.Literal lit -> constant_of_literal t expr.loc resolved lit
| Core.Nil when Analysis.resolved_is_pointerish resolved ->
Some (Llvm.const_null (llvm_type_of_resolved t resolved))
| Core.Zero -> Some (zero_constant t resolved)
| Core.As cast ->
Option.bind
(constant_of_expr t cast.value.inner)
Expand Down Expand Up @@ -2000,6 +2001,7 @@ and emit_expr t (expr : Core.expression) =
| None -> fail ~loc:expr.loc "failed to lower size expression")
| Core.Nil ->
Llvm.const_null (llvm_type_of_resolved t (expr_resolved_type t expr))
| Core.Zero -> zero_constant t (expr_resolved_type t expr)
| Core.Match match_expr -> emit_match t expr match_expr
| Core.BoxExpr inner -> emit_box_expr t expr inner
| Core.BoxType ty -> emit_box_type_expr t expr ty
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ast/pretty.ml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ and pp_surface_expression fmt (expr : Surface.expression) =
| SizeExpr inner -> fprintf fmt "SizeExpr(%a)" pp_surface_expression inner
| SizeType ty -> fprintf fmt "SizeType(%a)" pp_surface_type ty
| Nil -> fprintf fmt "Nil"
| Zero -> fprintf fmt "Zero"
| If ifx ->
fprintf fmt "@[<hv 2>If(@,cond=%a,@ then=%a,@ else=%a@,)@]"
pp_surface_expression ifx.value.cond pp_surface_block ifx.value.then_branch
Expand Down Expand Up @@ -387,6 +388,7 @@ and pp_core_expression fmt (expr : Core.expression) =
| SizeExpr inner -> fprintf fmt "SizeExpr(%a)" pp_core_expression inner
| SizeType ty -> fprintf fmt "SizeType(%a)" pp_core_type ty
| Nil -> fprintf fmt "Nil"
| Zero -> fprintf fmt "Zero"
| Match m ->
fprintf fmt "@[<hv 2>Match(@,expr=%a,@ arms=%a@,)@]" pp_core_expression
m.value.expr
Expand Down
1 change: 1 addition & 0 deletions src/lib/ast/surface_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ and expression_desc =
| SizeExpr of expression
| SizeType of haven_type
| Nil
| Zero
| If of if_expr
| Match of match_expr
| BoxExpr of expression
Expand Down
1 change: 1 addition & 0 deletions src/lib/cst/cst.ml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ and expression_desc =
| SizeExpr of expression
| SizeType of haven_type
| Nil
| Zero
| If of if_expr
| Match of match_expr
| BoxExpr of expression
Expand Down
1 change: 1 addition & 0 deletions src/lib/cst/emit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ let rec emit_expression ?(ctx_prec = 0) ~indent ~comments fmt expr =
fprintf fmt "size(%a)" (emit_expression ~ctx_prec:0 ~indent ~comments) e
| SizeType t -> fprintf fmt "size<%a>" emit_type t
| Nil -> fprintf fmt "nil"
| Zero -> fprintf fmt "zero"
| If i -> emit_if_expr ~indent ~comments fmt i
| Match m -> emit_match_expr ~indent ~comments fmt m
| BoxExpr e ->
Expand Down
1 change: 1 addition & 0 deletions src/lib/cst/locate.ml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ and walk_expression predicate acc expr =
| SizeExpr e -> walk_expression predicate acc e
| SizeType t -> walk_haven_type predicate acc t
| Nil -> acc
| Zero -> acc
| If i -> walk_if_expr predicate acc i
| Match m ->
let acc = add_if predicate (MatchExpr m) acc in
Expand Down
1 change: 1 addition & 0 deletions src/lib/cst/pretty.ml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ and pp_expression fmt expr =
| SizeExpr e -> fprintf fmt "SizeExpr(%a)" pp_expression e
| SizeType t -> fprintf fmt "SizeType(%a)" pp_type t
| Nil -> fprintf fmt "Nil"
| Zero -> fprintf fmt "Zero"
| If i -> pp_if_expr fmt i
| Match m -> pp_match_expr fmt m
| BoxExpr e -> fprintf fmt "Box(expr=%a)" pp_expression e
Expand Down
3 changes: 2 additions & 1 deletion src/lib/parser/grammar.mly
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

(* Main keywords *)
%token PUB FN MUT IF ELSE LET WHILE UNTIL BREAK CONTINUE MATCH AS ITER
%token LOAD RET STRUCT TYPE NIL DEFER IMPURE ENUM IMPORT CIMPORT SIZE
%token LOAD RET STRUCT TYPE NIL ZERO DEFER IMPURE ENUM IMPORT CIMPORT SIZE
%token BOX UNBOX INTRINSIC FOREIGN DATA STATE VEC MAT FUNCTION
%token VAFUNCTION CELL REF EXTEND WITH CONSTRUCT DESTRUCT

Expand Down Expand Up @@ -261,6 +261,7 @@ primary:
| SIZE LT t=haven_type GT { mk_expr $startpos $endpos (SizeType t) }
| SIZE LPAREN e=expr RPAREN { mk_expr $startpos $endpos (SizeExpr e) }
| NIL { mk_expr $startpos $endpos Nil }
| ZERO { mk_expr $startpos $endpos Zero }
;

init: LBRACE exprs=separated_nonempty_list(COMMA, expr) RBRACE { mk_loc $startpos $endpos { exprs } } ;
Expand Down
1 change: 1 addition & 0 deletions src/lib/parser/parser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let keywords =
("void", Grammar.VOID_TYPE);
("type", Grammar.TYPE);
("nil", Grammar.NIL);
("zero", Grammar.ZERO);
("defer", Grammar.DEFER);
("impure", Grammar.IMPURE);
("enum", Grammar.ENUM);
Expand Down
21 changes: 21 additions & 0 deletions src/test/test_llvm_ir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,27 @@ let run () =
assert_true "external declarations should not synthesize startup initialization"
(not (string_contains external_ir "@__haven_global_init"));

let zero_aggregate_ir =
emit_ir "pub state i32[4] values = zero;\npub fn read() -> i32 { values[3] }"
in
assert_true "aggregate zero initializers should emit zero-filled definitions"
(string_contains zero_aggregate_ir "@values = global [4 x i32] zeroinitializer");

let zero_struct_ir =
emit_ir
"type Buffer = struct { i8* ptr; u64 length; };\npub state Buffer buffer = zero;"
in
assert_true "struct zero initializers should recursively include pointer fields"
(string_contains zero_struct_ir
"@buffer = global %haven.struct.Buffer zeroinitializer");

let local_zero_ir =
emit_ir
"pub impure fn read() -> i32 { let mut i32[4] values = zero; values[3] = 7; values[0] }"
in
assert_true "local aggregate zero initializers should store a zero-filled value"
(string_contains local_zero_ir "store [4 x i32] zeroinitializer");

let constant_cast_ir =
emit_ir
"data u32 ZERO = as<u32>(0);\npub fn main() -> u32 { ZERO }"
Expand Down
3 changes: 3 additions & 0 deletions src/test/test_parser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ let run () =
assert_parse_ok "initializer without trailing comma"
"type Thing = struct { i32 value; }; pub fn main() -> i32 { let Thing thing = { 1 }; thing.value }";

assert_parse_ok "aggregate zero initializer"
"pub state i32[4] values = zero;";

assert_parse_ok "extend lifecycle block"
{|
type Buffer = struct {
Expand Down
Loading
Loading