@@ -49,57 +49,119 @@ def handle_assign_allocation(compilation_context, builder, stmt, local_sym_tab):
4949 )
5050 continue
5151
52- var_name = target .id
52+ _bind_name (
53+ compilation_context ,
54+ stmt ,
55+ target ,
56+ local_sym_tab ,
57+ lambda var_name : _allocate_for_value (
58+ builder , var_name , rval , local_sym_tab , compilation_context
59+ ),
60+ )
5361
54- # Already bound in this scope: a parameter, an earlier assignment, or a
55- # `global` declaration (whose slot is the GlobalVariable). No slot needed.
56- if var_name in local_sym_tab :
57- logger .debug (f"'{ var_name } ' already bound, no allocation needed" )
58- continue
5962
60- # Not declared `global`, yet named like one: Python creates a local
61- # that shadows the global for the whole function body, and leaves the
62- # global untouched. Do the same.
63- shadows_global = var_name in compilation_context .bpf_globals
64- if shadows_global :
65- logger .info (
66- f"'{ var_name } ' is assigned without a 'global' declaration, so it "
67- f"is a local shadowing the @bpfglobal of the same name"
68- )
63+ def handle_ann_assign_allocation (compilation_context , builder , stmt , local_sym_tab ):
64+ """Handle memory allocation for annotated assignment (`x: c_int32 = 0`).
6965
70- # Determine type and allocate based on rval
71- if isinstance (rval , ast .Call ):
72- _allocate_for_call (
73- builder , var_name , rval , local_sym_tab , compilation_context
74- )
75- elif isinstance (rval , ast .Constant ):
76- _allocate_for_constant (builder , var_name , rval , local_sym_tab )
77- elif isinstance (rval , ast .BinOp ):
78- _allocate_for_binop (
79- builder , var_name , rval , local_sym_tab , compilation_context
80- )
81- elif isinstance (rval , ast .Name ):
82- # Variable-to-variable assignment (b = a)
83- _allocate_for_name (
84- builder , var_name , rval , local_sym_tab , compilation_context
85- )
86- elif isinstance (rval , ast .Attribute ):
87- # Struct field-to-variable assignment (a = dat.fld)
88- _allocate_for_attribute (
89- builder , var_name , rval , local_sym_tab , compilation_context
90- )
91- else :
92- logger .warning (
93- f"Unsupported assignment value type for { var_name } : { type (rval ).__name__ } "
94- )
66+ The annotation, not the value, types the slot: `x: c_int32 = 0` is an i32
67+ even though the literal alone would give an i64. With no value
68+ (`x: c_int64`) the slot is still made, and the name stays unbound until
69+ something assigns it, as in Python.
70+ """
71+ logger .info (f"Handling annotated assignment for allocation: { ast .dump (stmt )} " )
9572
96- if shadows_global and var_name in local_sym_tab :
97- # Where the binding ends, so that a read above it is reported the
98- # way Python reports it. end_lineno, not lineno, so a read on a
99- # continuation line of a multi-line binding counts as above it too.
100- local_sym_tab [var_name ].shadows_global_from = (
101- getattr (stmt , "end_lineno" , None ) or target .lineno
102- )
73+ if not isinstance (stmt .target , ast .Name ):
74+ raise SyntaxError (
75+ f"annotated assignment on line { stmt .lineno } must target a plain "
76+ f"name, got { type (stmt .target ).__name__ } "
77+ )
78+
79+ ir_type = annotation_to_ir (stmt .annotation , stmt .lineno )
80+ _bind_name (
81+ compilation_context ,
82+ stmt ,
83+ stmt .target ,
84+ local_sym_tab ,
85+ lambda var_name : local_sym_tab .__setitem__ (
86+ var_name ,
87+ LocalSymbol (_allocate_with_type (builder , var_name , ir_type ), ir_type ),
88+ ),
89+ )
90+
91+
92+ def annotation_to_ir (annotation , lineno ):
93+ """IR type for a ctypes annotation, written `c_int32` or `ctypes.c_int32`."""
94+ if isinstance (annotation , ast .Name ):
95+ name = annotation .id
96+ elif isinstance (annotation , ast .Attribute ):
97+ name = annotation .attr
98+ else :
99+ name = None
100+ if name is None or not is_ctypes (name ):
101+ raise SyntaxError (
102+ f"unsupported annotation on line { lineno } : { ast .unparse (annotation )} "
103+ f"(annotate locals with a ctypes integer type such as c_int64)"
104+ )
105+ return ctypes_to_ir (name )
106+
107+
108+ def _bind_name (compilation_context , stmt , target , local_sym_tab , allocate ):
109+ """What every statement that binds a bare name shares, around the
110+ statement-specific `allocate(var_name)` that makes the slot.
111+
112+ A name already bound needs no new slot. A name that is also a @bpfglobal
113+ but was not declared `global` becomes a local shadowing it, and records
114+ where its binding ends so a read above it is reported as Python would.
115+ """
116+ var_name = target .id
117+
118+ # Already bound in this scope: a parameter, an earlier assignment, or a
119+ # `global` declaration (whose slot is the GlobalVariable). No slot needed.
120+ if var_name in local_sym_tab :
121+ logger .debug (f"'{ var_name } ' already bound, no allocation needed" )
122+ return
123+
124+ # Not declared `global`, yet named like one: Python creates a local
125+ # that shadows the global for the whole function body, and leaves the
126+ # global untouched. Do the same.
127+ shadows_global = var_name in compilation_context .bpf_globals
128+ if shadows_global :
129+ logger .info (
130+ f"'{ var_name } ' is assigned without a 'global' declaration, so it "
131+ f"is a local shadowing the @bpfglobal of the same name"
132+ )
133+
134+ allocate (var_name )
135+
136+ if shadows_global and var_name in local_sym_tab :
137+ # Where the binding ends, so that a read above it is reported the
138+ # way Python reports it. end_lineno, not lineno, so a read on a
139+ # continuation line of a multi-line binding counts as above it too.
140+ local_sym_tab [var_name ].shadows_global_from = (
141+ getattr (stmt , "end_lineno" , None ) or target .lineno
142+ )
143+
144+
145+ def _allocate_for_value (builder , var_name , rval , local_sym_tab , compilation_context ):
146+ """Allocate a slot for `var_name = rval`, typed from the value."""
147+ if isinstance (rval , ast .Call ):
148+ _allocate_for_call (builder , var_name , rval , local_sym_tab , compilation_context )
149+ elif isinstance (rval , ast .Constant ):
150+ _allocate_for_constant (builder , var_name , rval , local_sym_tab )
151+ elif isinstance (rval , ast .BinOp ):
152+ _allocate_for_binop (builder , var_name , rval , local_sym_tab , compilation_context )
153+ elif isinstance (rval , ast .Name ):
154+ # Variable-to-variable assignment (b = a)
155+ _allocate_for_name (builder , var_name , rval , local_sym_tab , compilation_context )
156+ elif isinstance (rval , ast .Attribute ):
157+ # Struct field-to-variable assignment (a = dat.fld)
158+ _allocate_for_attribute (
159+ builder , var_name , rval , local_sym_tab , compilation_context
160+ )
161+ else :
162+ logger .warning (
163+ f"Unsupported assignment value type for { var_name } : { type (rval ).__name__ } "
164+ )
103165
104166
105167def _allocate_for_call (builder , var_name , rval , local_sym_tab , compilation_context ):
0 commit comments