Skip to content

Commit 70dc817

Browse files
committed
feat: allow IDL for loop iteration variables to be const
1 parent 9912f84 commit 70dc817

File tree

9 files changed

+219
-37
lines changed

9 files changed

+219
-37
lines changed

doc/idl.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,21 @@ for (U32 i = 0; i < 32; i++) {
761761
}
762762
----
763763

764+
for loop iterations may be declared const if and only if all assignments to the variable are const
765+
766+
[source,idl]
767+
----
768+
# OK; all updates to I would be const in a loop unrolling
769+
for (U32 I = 0; I < 32; I++) {
770+
X[I] = 0;
771+
}
772+
773+
# raises a type error
774+
for (U32 I = 0; I < 32; I = I + mutable_variable) {
775+
X[I] = 0;
776+
}
777+
----
778+
764779
== Functions
765780

766781
The basic form of a function declaration is below.

spec/std/isa/isa/globals.isa

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,8 +2569,8 @@ function read_memory {
25692569
# misaligned, must break into multiple reads
25702570
if (MISALIGNED_SPLIT_STRATEGY == "by_byte") {
25712571
Bits<LEN> result = 0;
2572-
for (U32 i = 0; i <= (LEN/8); i++) {
2573-
result = result | (read_memory_aligned<8>(virtual_address + i, encoding) << (8*i));
2572+
for (U32 I = 0; I <= (LEN/8); I++) {
2573+
result = result | (read_memory_aligned<8>(virtual_address + I, encoding) << (8*I));
25742574
}
25752575
return result;
25762576
} else if (MISALIGNED_SPLIT_STRATEGY == "custom") {

tools/ruby-gems/idlc/lib/idlc.rb

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class IdlParser < Treetop::Runtime::CompiledParser; end
4444
module Idl
4545
# the Idl compiler
4646
class Compiler
47+
extend T::Sig
48+
4749
attr_reader :parser
4850

4951
def initialize
@@ -111,6 +113,50 @@ def compile_file(path)
111113
ast
112114
end
113115

116+
sig { params(loop: String, symtab: SymbolTable, pass_error: T::Boolean).returns(ForLoopAst) }
117+
def compile_for_loop(loop, symtab, pass_error: false)
118+
m = @parser.parse(loop, root: :for_loop)
119+
if m.nil?
120+
raise SyntaxError, <<~MSG
121+
While parsing #{loop}:#{@parser.failure_line}:#{@parser.failure_column}
122+
123+
#{@parser.failure_reason}
124+
MSG
125+
end
126+
127+
ast = m.to_ast
128+
ast.set_input_file("[LOOP]", 0)
129+
value_result = ast.value_try do
130+
ast.freeze_tree(symtab)
131+
end
132+
if value_result == :unknown_value
133+
raise AstNode::TypeError, "Bad literal value" if pass_error
134+
135+
warn "Compiling #{loop}"
136+
warn "Bad literal value"
137+
exit 1
138+
end
139+
begin
140+
ast.type_check(symtab)
141+
rescue AstNode::TypeError => e
142+
raise e if pass_error
143+
144+
warn "Compiling #{loop}"
145+
warn e.what
146+
warn T.must(e.backtrace).join("\n")
147+
exit 1
148+
rescue AstNode::InternalError => e
149+
raise e if pass_error
150+
151+
warn "Compiling #{loop}"
152+
warn e.what
153+
warn T.must(e.backtrace).join("\n")
154+
exit 1
155+
end
156+
157+
ast
158+
end
159+
114160
# compile a function body, and return the abstract syntax tree
115161
#
116162
# @param body [String] Function body source code
@@ -164,7 +210,7 @@ def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file
164210

165211
warn "In function #{name}:"
166212
warn e.what
167-
warn T.must(e.backtrace).to_s
213+
warn T.must(e.backtrace).join("\n")
168214
exit 1
169215
ensure
170216
cloned_symtab.pop
@@ -234,7 +280,7 @@ def type_check(ast, symtab, what)
234280
rescue AstNode::InternalError => e
235281
warn "While type checking #{what}:"
236282
warn e.what
237-
warn T.must(e.backtrace).to_s
283+
warn T.must(e.backtrace).join("\n")
238284
exit 1
239285
end
240286

@@ -270,14 +316,14 @@ def compile_expression(expression, symtab, pass_error: false)
270316

271317
warn "Compiling #{expression}"
272318
warn e.what
273-
warn T.must(e.backtrace).to_s
319+
warn T.must(e.backtrace).join("\n")
274320
exit 1
275321
rescue AstNode::InternalError => e
276322
raise e if pass_error
277323

278324
warn "Compiling #{expression}"
279325
warn e.what
280-
warn T.must(e.backtrace).to_s
326+
warn T.must(e.backtrace).join("\n")
281327
exit 1
282328
end
283329

tools/ruby-gems/idlc/lib/idlc/ast.rb

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ def execute(symtab); end
510510
def execute_unknown(symtab); end
511511
end
512512

513+
ExecutableAst = T.type_alias { T.all(Executable, AstNode) }
514+
513515
# interface for nodes that *might* return a value in a function body
514516
module Returns
515517
extend T::Sig
@@ -1998,8 +2000,11 @@ def const_eval?(symtab)
19982000
end
19992001
end
20002002

2001-
def lhs = @children[0]
2002-
def rhs = @children[1]
2003+
sig { returns(IdAst) }
2004+
def lhs = T.cast(@children.fetch(0), IdAst)
2005+
2006+
sig { returns(RvalueAst) }
2007+
def rhs = T.cast(@children.fetch(1), RvalueAst)
20032008

20042009
def initialize(input, interval, lhs_ast, rhs_ast)
20052010
super(input, interval, [lhs_ast, rhs_ast])
@@ -2009,9 +2014,14 @@ def initialize(input, interval, lhs_ast, rhs_ast)
20092014
# @!macro type_check
20102015
def type_check(symtab)
20112016
lhs.type_check(symtab)
2012-
type_error "Cannot assign to a const" if lhs.type(symtab).const?
2017+
lhs_var = symtab.get(lhs.name)
2018+
type_error "Cannot assign to a const" if lhs_var.type.const? && !lhs_var.for_loop_iter?
20132019

20142020
rhs.type_check(symtab)
2021+
if lhs_var.type.const? && lhs_var.for_loop_iter?
2022+
# also check that the rhs is const_eval
2023+
type_error "Assignment would make iteration variable non-const" unless rhs.type(symtab).const?
2024+
end
20152025
unless rhs.type(symtab).convertable_to?(lhs.type(symtab))
20162026
type_error "Incompatible type in assignment (#{lhs.type(symtab)}, #{rhs.type(symtab)})"
20172027
end
@@ -2806,7 +2816,19 @@ def to_ast
28062816
ary_size_ast = send(:ary_size).empty? ? nil : send(:ary_size).expression.to_ast
28072817
VariableDeclarationWithInitializationAst.new(
28082818
input, interval,
2809-
send(:type_name).to_ast, send(:id).to_ast, ary_size_ast, send(:rval).to_ast
2819+
send(:type_name).to_ast, send(:id).to_ast, ary_size_ast, send(:rval).to_ast,
2820+
false
2821+
)
2822+
end
2823+
end
2824+
2825+
class ForLoopIterationVariableDeclarationSyntaxNode < SyntaxNode
2826+
def to_ast
2827+
ary_size_ast = send(:ary_size).empty? ? nil : send(:ary_size).expression.to_ast
2828+
VariableDeclarationWithInitializationAst.new(
2829+
input, interval,
2830+
send(:type_name).to_ast, send(:id).to_ast, ary_size_ast, send(:rval).to_ast,
2831+
true
28102832
)
28112833
end
28122834
end
@@ -2854,16 +2876,18 @@ def id = lhs.text_value
28542876
type_name_ast: TypeNameAst,
28552877
var_write_ast: IdAst,
28562878
ary_size: T.nilable(RvalueAst),
2857-
rval_ast: RvalueAst
2879+
rval_ast: RvalueAst,
2880+
is_for_loop_iteration_var: T::Boolean
28582881
).void
28592882
}
2860-
def initialize(input, interval, type_name_ast, var_write_ast, ary_size, rval_ast)
2883+
def initialize(input, interval, type_name_ast, var_write_ast, ary_size, rval_ast, is_for_loop_iteration_var)
28612884
if ary_size.nil?
28622885
super(input, interval, [type_name_ast, var_write_ast, rval_ast])
28632886
else
28642887
super(input, interval, [type_name_ast, var_write_ast, rval_ast, ary_size])
28652888
end
28662889
@global = false
2890+
@for_iter_var = is_for_loop_iteration_var
28672891
end
28682892

28692893
def make_global
@@ -2909,16 +2933,16 @@ def type_check(symtab)
29092933
if decl_type.const?
29102934
# this is a constant; ensure we are assigning a constant value
29112935
value_result = value_try do
2912-
symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone, rhs.value(symtab)))
2936+
symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone, rhs.value(symtab), for_loop_iter: @for_iter_var))
29132937
end
29142938
value_else(value_result) do
29152939
unless rhs.type(symtab).const?
29162940
type_error "Declaring constant (#{lhs.name}) with a non-constant value (#{rhs.text_value})"
29172941
end
2918-
symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone))
2942+
symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone, for_loop_iter: @for_iter_var))
29192943
end
29202944
else
2921-
symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone))
2945+
symtab.add(lhs.text_value, Var.new(lhs.text_value, decl_type.clone, for_loop_iter: @for_iter_var))
29222946
end
29232947

29242948
lhs.type_check(symtab)
@@ -2935,21 +2959,21 @@ def add_symbol(symtab)
29352959
if lhs.text_value[0] == T.must(lhs.text_value[0]).upcase
29362960
# const, add the value if it's known
29372961
value_result = value_try do
2938-
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs.value(symtab)))
2962+
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs.value(symtab), for_loop_iter: @for_iter_var))
29392963
end
29402964
value_else(value_result) do
2941-
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab)))
2965+
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), for_loop_iter: @for_iter_var))
29422966
end
29432967
else
29442968
# mutable globals never have a compile-time value
2945-
symtab.add!(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab)))
2969+
symtab.add!(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), for_loop_iter: @for_iter_var))
29462970
end
29472971
else
29482972
value_result = value_try do
2949-
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs.value(symtab)))
2973+
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs.value(symtab), for_loop_iter: @for_iter_var))
29502974
end
29512975
value_else(value_result) do
2952-
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab)))
2976+
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), for_loop_iter: @for_iter_var))
29532977
end
29542978
end
29552979
end
@@ -2964,15 +2988,15 @@ def execute(symtab)
29642988
rhs_value = rhs.value(symtab)
29652989
end
29662990
value_else(value_result) do
2967-
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), nil))
2991+
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), nil, for_loop_iter: @for_iter_var))
29682992
value_error "value of right-hand side of variable initialization is unknown"
29692993
end
29702994
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), rhs_value))
29712995
end
29722996

29732997
# @!macro execute_unknown
29742998
def execute_unknown(symtab)
2975-
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), nil))
2999+
symtab.add(lhs.text_value, Var.new(lhs.text_value, lhs_type(symtab), nil, for_loop_iter: @for_iter_var))
29763000
end
29773001

29783002
# @!macro to_idl
@@ -3364,7 +3388,7 @@ def type_check(symtab)
33643388
lhs_type = lhs.type(symtab)
33653389
type_error "Unsupported type for left shift: #{lhs_type}" unless lhs_type.kind == :bits
33663390
type_error "Unsupported shift for left shift: #{rhs_type}" unless rhs_type.kind == :bits
3367-
type_error "Widening shift amount must be constant" unless rhs_type.const?
3391+
type_error "Widening shift amount must be constant (if it's not, the width of the result is unknowable)." unless rhs_type.const?
33683392
elsif [">>", ">>>"].include?(op)
33693393
rhs_type = rhs.type(symtab)
33703394
lhs_type = lhs.type(symtab)
@@ -4149,14 +4173,18 @@ class PostDecrementExpressionAst < AstNode
41494173
sig { override.params(symtab: SymbolTable).returns(T::Boolean) }
41504174
def const_eval?(symtab) = rval.const_eval?(symtab)
41514175

4152-
def rval = @children[0]
4176+
sig { returns(T.any(IntLiteralAst, BuiltinVariableAst, StringLiteralAst, IdAst)) }
4177+
def rval = T.cast(@children.fetch(0), T.any(IntLiteralAst, BuiltinVariableAst, StringLiteralAst, IdAst))
41534178

41544179
def initialize(input, interval, rval)
41554180
super(input, interval, [rval])
41564181
end
41574182

41584183
def type_check(symtab)
41594184
rval.type_check(symtab)
4185+
rval_immutable =
4186+
rval.is_a?(IdAst) && (rval.type(symtab).const? && !symtab.get(T.cast(rval, IdAst).name).for_loop_iter?)
4187+
type_error "Cannot decrement a const variable" if rval_immutable
41604188
type_error "Post decement must be integral" unless rval.type(symtab).integral?
41614189
end
41624190

@@ -4268,6 +4296,9 @@ def initialize(input, interval, rval)
42684296
def type_check(symtab)
42694297
rval.type_check(symtab)
42704298
var = symtab.get(rval.text_value)
4299+
rval_immutable =
4300+
rval.is_a?(IdAst) && (rval.type(symtab).const? && !var.for_loop_iter?)
4301+
type_error "Cannot increment a const variable" if rval_immutable
42714302
type_error "Post increment variable must be integral" unless var.type.integral?
42724303
end
42734304

@@ -6482,7 +6513,7 @@ class ForLoopSyntaxNode < SyntaxNode
64826513
def to_ast
64836514
ForLoopAst.new(
64846515
input, interval,
6485-
send(:single_declaration_with_initialization).to_ast,
6516+
send(:for_loop_iteration_variable_declaration).to_ast,
64866517
send(:condition).to_ast,
64876518
send(:action).to_ast,
64886519
send(:stmts).elements.map(&:s).map(&:to_ast)
@@ -6502,10 +6533,17 @@ def const_eval?(symtab)
65026533
stmts.all? { |stmt| stmt.const_eval?(symtab) }
65036534
end
65046535

6505-
def init = @children[0]
6506-
def condition = @children[1]
6507-
def update = @children[2]
6508-
def stmts = @children[3..]
6536+
sig { returns(VariableDeclarationWithInitializationAst) }
6537+
def init = T.cast(@children.fetch(0), VariableDeclarationWithInitializationAst)
6538+
6539+
sig { returns(RvalueAst) }
6540+
def condition = T.cast(@children.fetch(1), RvalueAst)
6541+
6542+
sig { returns(ExecutableAst) }
6543+
def update = T.cast(@children.fetch(2), ExecutableAst)
6544+
6545+
sig { returns(T::Array[T.any(StatementAst, ReturnStatementAst, IfAst, ForLoopAst)]) }
6546+
def stmts = T.cast(@children[3..], T::Array[T.any(StatementAst, ReturnStatementAst, IfAst, ForLoopAst)])
65096547

65106548
def initialize(input, interval, init, condition, update, stmts)
65116549
super(input, interval, [init, condition, update] + stmts)
@@ -6615,7 +6653,9 @@ def execute_unknown(symtab)
66156653
init.execute_unknown(symtab)
66166654

66176655
stmts.each do |s|
6618-
s.execute_unknown(symtab)
6656+
unless s.is_a?(ReturnStatementAst)
6657+
s.execute_unknown(symtab)
6658+
end
66196659
end
66206660
update.execute_unknown(symtab)
66216661
end

tools/ruby-gems/idlc/lib/idlc/idl.treetop

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,11 @@ grammar Idl
501501
type_name space+ id space* ary_size:ary_size_decl? space* '=' space* rval:expression <Idl::VariableDeclarationWithInitializationSyntaxNode>
502502
end
503503

504+
# same as single_declaration_with_initialization, but treated specially for semantics (iteration variable can be treated as const even if it's written to, as long as it would be const in a loop unrolling)
505+
rule for_loop_iteration_variable_declaration
506+
type_name space+ id space* ary_size:ary_size_decl? space* '=' space* rval:expression <Idl::ForLoopIterationVariableDeclarationSyntaxNode>
507+
end
508+
504509
rule declaration
505510
type_name space+ first:id space* rest:(space* ',' space* id)+ space* <Idl::MultiVariableDeclarationSyntaxNode>
506511
/
@@ -568,9 +573,9 @@ grammar Idl
568573
end
569574

570575
rule for_loop
571-
'for' space* '(' space* single_declaration_with_initialization space* ';' space* condition:expression space* ';' space* action:(assignment / post_inc / post_dec) space* ')' space* '{' space*
576+
'for' space* '(' space* for_loop_iteration_variable_declaration space* ';' space* condition:expression space* ';' space* action:(assignment / post_inc / post_dec) space* ')' space* '{' space*
572577
stmts:(s:(statement / return_statement / function_if_block / for_loop) space*)+
573-
'}' <Idl::ForLoopSyntaxNode>
578+
'}' space* <Idl::ForLoopSyntaxNode>
574579
end
575580

576581
rule builtin_type_name

0 commit comments

Comments
 (0)