From c39a8b10169cdcfc2f3428b0e38ac044c2f02f0e Mon Sep 17 00:00:00 2001 From: florianmalicky Date: Mon, 3 Aug 2026 18:25:34 +0200 Subject: [PATCH] frontend: Add float support - Adds float-type definition - Adds annotation [ IEEE : ] for float-type - Adds annotation [ [sticky] fe flag : ] for registers - Adds new syntax for SymbolExpr: id::path::(params) - Adds new VIAM nodes and built-in types - FloatFormat node - Constant.FloatType - VIAM annotation for float exception flags - The built-in type FloatType, which is the type of FloatFormat - Adds constant-param-based type inference and checking for built-ins --- .../main/vadl/ast/AnnotationTable.java | 176 +++++++++++++--- vadl-frontend/main/vadl/ast/AstUtils.java | 4 +- .../main/vadl/ast/BehaviorLowering.java | 51 ++++- .../main/vadl/ast/ConstantEvaluator.java | 10 +- .../main/vadl/ast/MacroExpander.java | 16 +- vadl-frontend/main/vadl/ast/ModelRemover.java | 6 + vadl-frontend/main/vadl/ast/SymbolTable.java | 11 + vadl-frontend/main/vadl/ast/TypeChecker.java | 132 +++++++++--- vadl-frontend/main/vadl/ast/Ungrouper.java | 9 +- vadl-frontend/main/vadl/ast/ViamLowering.java | 13 +- .../main/vadl/ast/nodes/CallIndexExpr.java | 6 +- .../vadl/ast/nodes/DefinitionVisitor.java | 2 + .../vadl/ast/nodes/FloatTypeDefinition.java | 96 +++++++++ .../main/vadl/ast/nodes/IsCallExpr.java | 3 +- vadl-frontend/main/vadl/ast/nodes/IsId.java | 4 +- .../main/vadl/ast/nodes/IsSymExpr.java | 3 +- .../vadl/ast/nodes/RecursiveAstVisitor.java | 8 + .../main/vadl/ast/nodes/SymbolExpr.java | 45 +++-- .../main/vadl/ast/nodes/TypeLiteral.java | 4 +- vadl-frontend/main/vadl/ast/vadl.ATG | 41 +++- .../typechecker/invalidBuiltitinCalls.vadl | 2 +- vadl/main/vadl/dump/InfoUtils.java | 3 +- vadl/main/vadl/types/BuiltInTable.java | 190 ++++++++++++++---- vadl/main/vadl/types/FloatStatusType.java | 54 +++++ vadl/main/vadl/types/FloatType.java | 30 +++ vadl/main/vadl/types/RelationType.java | 27 ++- vadl/main/vadl/types/Type.java | 100 ++++++++- .../functionInterfaces/QuadConsumer.java | 51 +++++ vadl/main/vadl/viam/Constant.java | 86 +++++++- vadl/main/vadl/viam/DefinitionVisitor.java | 14 ++ vadl/main/vadl/viam/FloatExceptionFlag.java | 56 ++++++ vadl/main/vadl/viam/FloatFormat.java | 102 ++++++++++ .../vadl/viam/InstructionSetArchitecture.java | 11 + .../viam/annotations/FloatFlagAnnotation.java | 63 ++++++ .../viam/graph/dependency/BuiltInCall.java | 48 ++++- .../IssTensorAssignmentToForallPassTest.java | 1 + .../CanonicalizationPassTest.java | 1 + 37 files changed, 1313 insertions(+), 166 deletions(-) create mode 100644 vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java create mode 100644 vadl/main/vadl/types/FloatStatusType.java create mode 100644 vadl/main/vadl/types/FloatType.java create mode 100644 vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java create mode 100644 vadl/main/vadl/viam/FloatExceptionFlag.java create mode 100644 vadl/main/vadl/viam/FloatFormat.java create mode 100644 vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java diff --git a/vadl-frontend/main/vadl/ast/AnnotationTable.java b/vadl-frontend/main/vadl/ast/AnnotationTable.java index 7659bd8dd..640c2bdeb 100644 --- a/vadl-frontend/main/vadl/ast/AnnotationTable.java +++ b/vadl-frontend/main/vadl/ast/AnnotationTable.java @@ -24,6 +24,7 @@ import com.google.errorprone.annotations.concurrent.LazyInit; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -47,6 +48,7 @@ import vadl.ast.nodes.DerivedFormatField; import vadl.ast.nodes.EncodingDefinition; import vadl.ast.nodes.Expr; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.FormatField; import vadl.ast.nodes.GroupDefinition; import vadl.ast.nodes.Identifier; @@ -69,6 +71,7 @@ import vadl.types.BitsType; import vadl.types.Type; import vadl.utils.Pair; +import vadl.utils.functionInterfaces.QuadConsumer; import vadl.utils.functionInterfaces.TriConsumer; import vadl.viam.Abi; import vadl.viam.ArtificialResource; @@ -77,6 +80,8 @@ import vadl.viam.Counter; import vadl.viam.Encoding; import vadl.viam.Endianness; +import vadl.viam.FloatExceptionFlag; +import vadl.viam.FloatFormat; import vadl.viam.Format; import vadl.viam.Group; import vadl.viam.Instruction; @@ -204,7 +209,7 @@ public class AnnotationTable { // this handled in the VIAM lowering when constructing the ArtificialResource .build(); - annotationOn(RegisterDefinition.class, "execution state", FormatFieldAnnotation::new) + annotationOn(RegisterDefinition.class, "execution state", ExecutionStateAnnotation::new) .check((def, annotation, lowering) -> annotation.typeCheckTarget(def)) .applyAst((def, annotation) -> annotation.calcBitSlice(def)) .applyViam((def, annotation, lowering) -> { @@ -330,6 +335,56 @@ public class AnnotationTable { }) .build(); + /// FLOAT RELATED /// + + annotationOn(FloatTypeDefinition.class, "IEEE", ConstantAnnotation::new) + .applyAst((def, annotation) -> def.size = annotation.constant.value().intValue()) + .applyViam((def, annotation, lowering) -> { + var encoding = FloatFormat.Encoding.ieee(annotation.constant.value().intValue()); + ensure(encoding != null, + () -> error("Invalid IEEE encoding size", annotation) + .description("The following sizes are supported: %s", + Arrays.stream(FloatFormat.Encoding.values()) + .map(e -> Integer.toString(e.size)).collect(Collectors.joining(", "))) + ); + ((FloatFormat) def).setEncoding(encoding); + }).build(); + + QuadConsumer + applyViamFloatFlag; + applyViamFloatFlag = (reg, annotation, sticky, flag) -> { + var idx = annotation.index; + if (reg.hasAnnotation(vadl.viam.annotations.FloatFlagAnnotation.class)) { + var ann = reg.expectAnnotation(vadl.viam.annotations.FloatFlagAnnotation.class); + var setFlag = ann.get(idx); + ensure(setFlag == null, () -> error( + "Bit already mapped as " + (ann.isSticky(idx) ? "" : "non ") + + "sticky " + requireNonNull(setFlag).name + " flag", + annotation + )); + ann.set(idx, sticky, flag); + } else { + var ann = new vadl.viam.annotations.FloatFlagAnnotation(); + ann.set(idx, sticky, flag); + reg.addAnnotation(ann); + } + }; + + for (var flag : FloatExceptionFlag.values()) { + annotationOn(RegisterDefinition.class, "fe flag " + flag.name, FloatFlagAnnotation::new) + .check((def, annotation, lowering) -> annotation.typeCheckTarget(def)) + .applyViam((def, annotation, lowering) -> + applyViamFloatFlag.accept((RegisterTensor) def, annotation, false, flag)) + .build(); + + annotationOn(RegisterDefinition.class, "sticky fe flag " + flag.name, + FloatFlagAnnotation::new) + .check((def, annotation, lowering) -> annotation.typeCheckTarget(def)) + .applyViam((def, annotation, lowering) -> + applyViamFloatFlag.accept((RegisterTensor) def, annotation, true, flag)) + .build(); + } + /// PROCESSOR RELATED /// annotationOn(ProcessorDefinition.class, "htif", EnableAnnotation::new) @@ -1103,6 +1158,95 @@ public String usageString() { } } +/** + * An annotation that can be applied to registers of type {@link FormatType}. It can be used + * to reference one format field of the type. The bit-size of the format field must be 1. + * + *

Usage examples: + *

+ * [ sticky fe flag overflow : ov ]
+ * register reg : Format
+ * format Format : Bits<8> { ov [7], ... }
+ * 
+ */ +class FloatFlagAnnotation extends FormatFieldAnnotation { + + @LazyInit + Identifier field; + + @LazyInit + int index; + + @Override + void typeCheck(AnnotationDefinition definition, TypeChecker typeChecker) { + super.typeCheck(definition, typeChecker); + verifyValuesCnt(definition, 1); + field = (Identifier) definition.values.getFirst(); + } + + @Override + void typeCheckTarget(TypedNode target) { + super.typeCheckTarget(target); + var format = ((FormatType) target.type()).format; + var range = requireNonNull(format.getFieldRange(field.name)); + Diagnostic.ensure(range.from() == range.to(), () -> + error("Float flag can only be one bit", field)); + index = range.from(); + } + + @Override + String annotationName() { + return "Float exception flag annotation"; + } + + @Override + public String usageString() { + return "[ " + name + " : ]"; + } +} + +/** + * An annotation that can be applied to registers of type {@link BitsType}. If the register's + * type is a {@link FormatType}, then this annotation can be used to reference its format fields. + * + *

Usage examples: + *

+ * [ execution state ]
+ * register reg : Bits<8>
+ *
+ * [ execution state : f0, f1 ]
+ * register reg : Format
+ * format Format : Bits<8> { f0 [7], f1 [6], ... }
+ * 
+ */ +class ExecutionStateAnnotation extends FormatFieldAnnotation { + + void calcBitSlice(TypedNode target) { + if (fields.isEmpty()) { + var width = ((BitsType) target.type()).bitWidth(); + slice = Constant.BitSlice.of(width - 1, 0); + return; + } + var format = requireNonNull((FormatType) target.type()).format; + slice = new Constant.BitSlice( + fields.stream() + .map(field -> requireNonNull(format.getFieldRange(field.name))) + .map(range -> new Constant.BitSlice.Part(range.from(), range.to())) + .toArray(Constant.BitSlice.Part[]::new) + ); + } + + @Override + String annotationName() { + return "Execution state annotation"; + } + + @Override + public String usageString() { + return "[ " + name + " : , ... ]"; + } +} + /** * An annotation that can be applied to anything that has a type which is a {@link BitsType}. * If the target's type is a {@link FormatType}, then this annotation can be used to reference @@ -1118,7 +1262,7 @@ public String usageString() { * format Format : Bits<8> { f0 [7], f1 [6], ... } * */ -class FormatFieldAnnotation extends Annotation { +abstract class FormatFieldAnnotation extends Annotation { @LazyInit List fields; @@ -1145,14 +1289,14 @@ void typeCheckTarget(TypedNode target) { if (fields.isEmpty()) { Diagnostic.ensure(target.type() instanceof BitsType, () -> error("Annotation target has invalid type", this).description(""" - Execution state annotation can only be applied to simple \ - register definitions (no register files or tensors)""")); + %s can only be applied to simple register \ + definitions (no register files or tensors)""", annotationName())); return; } Diagnostic.ensure(target.type() instanceof FormatType, () -> error("Annotation target has invalid type", this).description(""" - Execution state annotation with format fields can only \ - be applied to register definitions with a format type""")); + %s with format fields can only be applied \ + to register definitions with a format type""", annotationName())); var format = ((FormatType) target.type()).format; fields.forEach(field -> { @@ -1170,25 +1314,7 @@ void typeCheckTarget(TypedNode target) { }); } - void calcBitSlice(TypedNode target) { - if (fields.isEmpty()) { - var width = ((BitsType) target.type()).bitWidth(); - slice = Constant.BitSlice.of(width - 1, 0); - return; - } - var format = requireNonNull((FormatType) target.type()).format; - slice = new Constant.BitSlice( - fields.stream() - .map(field -> requireNonNull(format.getFieldRange(field.name))) - .map(range -> new Constant.BitSlice.Part(range.from(), range.to())) - .toArray(Constant.BitSlice.Part[]::new) - ); - } - - @Override - public String usageString() { - return "[ " + name + " : , ... ]"; - } + abstract String annotationName(); } /** diff --git a/vadl-frontend/main/vadl/ast/AstUtils.java b/vadl-frontend/main/vadl/ast/AstUtils.java index c6ca9ae89..6ce7d3b8c 100644 --- a/vadl-frontend/main/vadl/ast/AstUtils.java +++ b/vadl-frontend/main/vadl/ast/AstUtils.java @@ -73,9 +73,7 @@ static BuiltInTable.BuiltIn getBuiltIn(String name, List argTypes) { name = "sdec"; } - String finalBuiltinName = name; - var matchingBuiltin = nameLookupTable.get(finalBuiltinName); - return matchingBuiltin; + return nameLookupTable.get(name); } static BuiltInTable.BuiltIn getOperatorBuiltIn(Operator operator, List argTypes) { diff --git a/vadl-frontend/main/vadl/ast/BehaviorLowering.java b/vadl-frontend/main/vadl/ast/BehaviorLowering.java index b138894d5..489a1c062 100644 --- a/vadl-frontend/main/vadl/ast/BehaviorLowering.java +++ b/vadl-frontend/main/vadl/ast/BehaviorLowering.java @@ -63,6 +63,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -125,13 +126,13 @@ import vadl.utils.BigIntUtils; import vadl.utils.Either; import vadl.utils.Pair; -import vadl.utils.SourceLocation; import vadl.utils.WithLocation; import vadl.viam.ArtificialResource; import vadl.viam.Constant; import vadl.viam.Counter; import vadl.viam.Definition; import vadl.viam.ExceptionDef; +import vadl.viam.FloatFormat; import vadl.viam.Format; import vadl.viam.Function; import vadl.viam.Instruction; @@ -912,6 +913,11 @@ private ExpressionNode visitIdentifiable(Expr expr) { return new ConstantNode(value); } + if (computedTarget instanceof FloatTypeDefinition floatType) { + var format = (FloatFormat) viamLowering.fetch(floatType).orElseThrow(); + return new ConstantNode(new Constant.FloatType(format)); + } + // Enum field if (computedTarget instanceof EnumerationDefinition.Entry enumField) { // Inline the value of the enum @@ -1376,6 +1382,26 @@ public ExpressionNode visitStageCall(CallIndexExpr expr, StageDefinition stageDe return new ReadStageOutputNode(output); } + private Constant visitConstArg(Expr expr) { + Node origin = null; + if (expr instanceof Identifier identifier) { + origin = requireNonNull(identifier.target()); + } else if (expr instanceof IdentifierPath path) { + origin = requireNonNull(path.target()); + } + + // TODO: the constant evaluator can only evaluate integers currently. Once other stuff like + // strings and float-types are supported, we do not need this function anymore and can + // directly call the constant evaluator. + // !!! There is a similar method in TypeChecker + if (origin instanceof FloatTypeDefinition floatType) { + var format = (FloatFormat) viamLowering.fetch(floatType).orElseThrow(); + return new Constant.FloatType(format); + } + + return constantEvaluator.eval(expr).toViamConstant(); + } + @Override public ExpressionNode visit(CallIndexExpr expr) { @@ -1385,6 +1411,12 @@ public ExpressionNode visit(CallIndexExpr expr) { return visitStageCall(expr, stageDefinition); } + var symbolArgs = expr.symbolArgs(); + + var constArgs = symbolArgs != null + ? symbolArgs.stream().map(this::visitConstArg).toList() + : List.of(); + var argGroups = expr.args(); final var args = new NodeList(AstUtils.argumentCount(argGroups)); AstUtils.forEachArgument(argGroups, arg -> args.add(this.fetch(arg))); @@ -1395,11 +1427,9 @@ public ExpressionNode visit(CallIndexExpr expr) { // Builtin Call if (expr.computedBuiltIn != null) { if (BuiltInTable.ASM_PARSER_BUILT_INS.contains(expr.computedBuiltIn)) { - exprBeforeSlice = new AsmBuiltInCall(expr.computedBuiltIn, args, - typeBeforeSlice); + exprBeforeSlice = new AsmBuiltInCall(expr.computedBuiltIn, args, typeBeforeSlice); } else { - exprBeforeSlice = new BuiltInCall(expr.computedBuiltIn, args, - typeBeforeSlice); + exprBeforeSlice = new BuiltInCall(expr.computedBuiltIn, constArgs, args, typeBeforeSlice); } } else { exprBeforeSlice = switch (expr.computedTarget()) { @@ -1432,9 +1462,8 @@ yield readRegisterTensorDirect(((Counter) lowered).registerTensor(), aliasArgs, } case MemoryDefinition memDef -> { - var sizeExpr = expr.target.size(); - var words = sizeExpr != null - ? constantEvaluator.eval(sizeExpr).value().intValueExact() + var words = symbolArgs != null + ? constantEvaluator.eval(symbolArgs.getFirst()).value().intValueExact() : 1; yield new ReadMemNode((Memory) viamLowering.fetch(memDef).orElseThrow(), words, args.getFirst(), typeBeforeSlice.asDataType()); @@ -1745,9 +1774,9 @@ public SubgraphContext visit(AssignmentStatement statement) { } }); - var sizeExpr = callTarget.target.size(); - callSize = sizeExpr != null - ? constantEvaluator.eval(sizeExpr).value().intValueExact() + var symbolArgs = callTarget.target.symbolArgs(); + callSize = symbolArgs != null + ? constantEvaluator.eval(symbolArgs.getFirst()).value().intValueExact() : null; } else if (statement.target instanceof Identifier identTarget) { targetDef = (vadl.ast.nodes.Definition) requireNonNull(identTarget.target()); diff --git a/vadl-frontend/main/vadl/ast/ConstantEvaluator.java b/vadl-frontend/main/vadl/ast/ConstantEvaluator.java index 247320919..e5543722b 100644 --- a/vadl-frontend/main/vadl/ast/ConstantEvaluator.java +++ b/vadl-frontend/main/vadl/ast/ConstantEvaluator.java @@ -164,10 +164,18 @@ public ConstantValue evalBuiltin(BuiltInTable.BuiltIn builtin, List (Constant) c.toViamConstant()).toList()) + .compute(List.of(), args.stream().map(c -> (Constant) c.toViamConstant()).toList()) .orElseThrow(() -> new EvaluationError( "Built-in function `%s` cannot be constant evaluated (yet).".formatted( builtin.name()), diff --git a/vadl-frontend/main/vadl/ast/MacroExpander.java b/vadl-frontend/main/vadl/ast/MacroExpander.java index d387ae9e1..614583c3a 100644 --- a/vadl-frontend/main/vadl/ast/MacroExpander.java +++ b/vadl-frontend/main/vadl/ast/MacroExpander.java @@ -73,6 +73,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallIndex; import vadl.ast.nodes.ForallStatement; @@ -522,7 +523,12 @@ public Expr visit(CastExpr expr) { @Override public Expr visit(SymbolExpr expr) { - return new SymbolExpr(expandExpr(expr.path), expandExpr(expr.size), copyLoc(expr.location)); + var symbolArgs = new ArrayList(expr.symbolArgs.size()); + for (var i = 0; i < expr.symbolArgs.size(); i++) { + var symbolArg = expr.symbolArgs.get(i); + symbolArgs.add(expandExpr(symbolArg)); + } + return new SymbolExpr(expandExpr(expr.path), symbolArgs, copyLoc(expr.location)); } @Override @@ -707,6 +713,14 @@ public Definition visit(ConstantDefinition definition) { ); } + @Override + public Definition visit(FloatTypeDefinition definition) { + return new FloatTypeDefinition( + expandExpr(definition.identifier), + copyLoc(definition.loc) + ); + } + @Override public Definition visit(FormatDefinition definition) { var fields = new ArrayList(definition.fields.size()); diff --git a/vadl-frontend/main/vadl/ast/ModelRemover.java b/vadl-frontend/main/vadl/ast/ModelRemover.java index 40b6525f7..c1f7b4c86 100644 --- a/vadl-frontend/main/vadl/ast/ModelRemover.java +++ b/vadl-frontend/main/vadl/ast/ModelRemover.java @@ -47,6 +47,7 @@ import vadl.ast.nodes.EncodingFormatField; import vadl.ast.nodes.EnumerationDefinition; import vadl.ast.nodes.ExceptionDefinition; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.FormatDefinition; import vadl.ast.nodes.FunctionDefinition; import vadl.ast.nodes.GroupDefinition; @@ -112,6 +113,11 @@ public Definition visit(ConstantDefinition definition) { return definition; } + @Override + public Definition visit(FloatTypeDefinition definition) { + return definition; + } + @Override public Definition visit(FormatDefinition definition) { return definition; diff --git a/vadl-frontend/main/vadl/ast/SymbolTable.java b/vadl-frontend/main/vadl/ast/SymbolTable.java index 74e1fa18c..8cefe39da 100644 --- a/vadl-frontend/main/vadl/ast/SymbolTable.java +++ b/vadl-frontend/main/vadl/ast/SymbolTable.java @@ -52,6 +52,7 @@ import vadl.ast.nodes.ExistsInExpr; import vadl.ast.nodes.ExistsInThenExpr; import vadl.ast.nodes.Expr; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -1178,6 +1179,16 @@ public Void visit(InstructionSetDefinition definition) { return null; } + @Override + public Void visit(FloatTypeDefinition definition) { + beforeTravel(definition); + // FloatTypeDefinition has no @Child fields, so it's not in NodeChildrenRegistry, + // and we need to manually visit annotations + definition.annotations.forEach(this::travel); + afterTravel(definition); + return null; + } + @Override public Void visit(InstructionDefinition definition) { // Import all symbols from the format. diff --git a/vadl-frontend/main/vadl/ast/TypeChecker.java b/vadl-frontend/main/vadl/ast/TypeChecker.java index f744482ce..e7f7c52dc 100644 --- a/vadl-frontend/main/vadl/ast/TypeChecker.java +++ b/vadl-frontend/main/vadl/ast/TypeChecker.java @@ -90,6 +90,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -175,6 +176,7 @@ import vadl.types.ConcreteRelationType; import vadl.types.DataType; import vadl.types.FetchResultType; +import vadl.types.FloatStatusType; import vadl.types.GroupType; import vadl.types.InstructionType; import vadl.types.MicroArchitectureType; @@ -936,21 +938,28 @@ List applyCastToArgs(List args) { /// A tiny custom cache for built-in function type checking. private static class BuiltInCheckCache { - private Map, BuiltInCheckResult>> store = - new HashMap<>(); + private Map, Map, BuiltInCheckResult>>> + store = new HashMap<>(); @Nullable - private BuiltInCheckResult get(BuiltInTable.BuiltIn builtIn, List argTypes) { + private BuiltInCheckResult get(BuiltInTable.BuiltIn builtIn, List constArgs, + List argTypes) { var inner = store.get(builtIn); if (inner == null) { return null; } - return inner.get(argTypes); + var innerInner = inner.get(constArgs); + if (innerInner == null) { + return null; + } + return innerInner.get(argTypes); } - private void put(BuiltInTable.BuiltIn builtIn, List argTypes, BuiltInCheckResult result) { + private void put(BuiltInTable.BuiltIn builtIn, List constArgs, List argTypes, + BuiltInCheckResult result) { var inner = store.computeIfAbsent(builtIn, k -> new HashMap<>()); - inner.put(argTypes, result); + var innerInner = inner.computeIfAbsent(constArgs, k -> new HashMap<>()); + innerInner.put(argTypes, result); } } @@ -960,18 +969,19 @@ private void put(BuiltInTable.BuiltIn builtIn, List argTypes, BuiltInCheck /// Check if the built-in function call, but doesn't care which kind of expression it arises from /// binary expressions, unary expressions or direct calls. /// The passed arguments have to be already checked! - private BuiltInCheckResult checkBuiltin(BuiltInTable.BuiltIn builtIn, List args, - WithLocation location) { + private BuiltInCheckResult checkBuiltin(BuiltInTable.BuiltIn builtIn, List constArgs, + List args, WithLocation location) { List argTypes = new ArrayList<>(args.size()); for (int i = 0; i < args.size(); i++) { argTypes.add(args.get(i).type()); } - var cached = builtInCheckCache.get(builtIn, argTypes); + var constArgValues = constArgs.stream().map(this::evalConstArg).toList(); + var cached = builtInCheckCache.get(builtIn, constArgValues, argTypes); if (cached != null) { return cached; } - var result = unCachedCheckBuiltin(builtIn, args, location); + var result = unCachedCheckBuiltin(builtIn, constArgValues, args, location); // We cannot cache if the result is a constant but not all input types were also constant. // This is quite rare but here we simply cannot determine the result type simply based on the @@ -980,18 +990,20 @@ private BuiltInCheckResult checkBuiltin(BuiltInTable.BuiltIn builtIn, List return result; } - builtInCheckCache.put(builtIn, argTypes, result); + builtInCheckCache.put(builtIn, constArgValues, argTypes, result); return result; } - private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, List args, + private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, + List constArgs, List args, WithLocation location) { - if (!(args.size() == builtIn.argTypeClasses().size() || (builtIn.signature().hasVarArgs() - && args.size() >= builtIn.argTypeClasses().size()))) { + int minArgCount = builtIn.argTypeClasses().size(); + if (!(args.size() == minArgCount + || (builtIn.signature().hasVarArgs() && args.size() >= minArgCount))) { throw addErrorAndStopChecking( error("Type Mismatch", location) .locationDescription(location, - "Expected %d arguments but got %d.", builtIn.argTypeClasses().size(), args.size()) + "Expected %d arguments but got %d.", minArgCount, args.size()) .build()); } @@ -1254,11 +1266,17 @@ private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, Li var originalArgTypes = argTypes; argTypes = args.stream().map(Expr::type).toList(); - - if (!builtIn.takes(argTypes)) { + if (!builtIn.takes(constArgs, argTypes)) { // FIXME: Further improve these error messages. var areSomeConst = originalArgTypes.stream().anyMatch(ConstantType.class::isInstance); - var calledTypes = String.join(", ", argTypes.stream().map(Type::toString).toList()); + var calledTypes = "(%s)".formatted( + String.join(", ", argTypes.stream().map(Type::toString).toList())); + if (!constArgs.isEmpty()) { + calledTypes = "<%s>%s".formatted( + String.join(", ", constArgs.stream().map(Constant::toString).toList()), + calledTypes + ); + } addErrorAndStopChecking( error("Type Mismatch", location) .locationDescription(location, "The builtin has the signature `%s` but got `%s`.", @@ -1268,7 +1286,42 @@ private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, Li .build()); } - return new BuiltInCheckResult(argTypes, builtIn.returns(argTypes)); + return new BuiltInCheckResult(argTypes, builtIn.returns(constArgs, argTypes)); + } + + private Constant evalConstArg(Expr expr) { + Node origin = null; + String name = null; + if (expr instanceof Identifier identifier) { + origin = requireNonNull(identifier.target()); + name = identifier.name; + } else if (expr instanceof IdentifierPath path) { + origin = requireNonNull(path.target()); + name = path.toString(); + } + + // TODO: the constant evaluator can only evaluate integers currently. Once other stuff like + // strings and float-types are supported, we do not need this function anymore and can + // directly call the constant evaluator. + // !!! There is a similar method in BehaviorLowering + if (origin instanceof FloatTypeDefinition floatType) { + check(floatType); + if (floatType.size == null) { + addErrorAndStopChecking( + error("Missing float-type encoding size", floatType) + .help("Annotate with e.g. `[ IEEE : ]`") + .build() + ); + } + return new Constant.FloatType(requireNonNull(floatType.size), requireNonNull(name)); + } + + return constantEvaluator.eval(expr).toViamConstant(); + } + + @Override + public Void visit(FloatTypeDefinition definition) { + return null; } @Override @@ -3216,6 +3269,12 @@ private void visitIdentifiable(Expr expr) { return; } + if (origin instanceof FloatTypeDefinition floatTypeDef) { + check(floatTypeDef); + expr.type = floatTypeDef.type(); + return; + } + if (origin instanceof RangeFormatField field) { // FIXME: Unfortonatley the format fields need to be specified in declare-after-use for now expr.type = field.type; @@ -3452,7 +3511,7 @@ public Void visit(BinaryExpr expr) { // operator and not in the builtin function we want to call. checkResult = checkLogicalBuiltIn(expr.left, expr.right, expr); } else { - checkResult = checkBuiltin(builtin, List.of(expr.left, expr.right), expr); + checkResult = checkBuiltin(builtin, List.of(), List.of(expr.left, expr.right), expr); } if (checkResult.castedArgTypes != null) { @@ -3839,7 +3898,7 @@ public Void visit(UnaryExpr expr) { }; expr.computedTarget = builtin; - var result = checkBuiltin(builtin, List.of(expr.operand), expr); + var result = checkBuiltin(builtin, List.of(), List.of(expr.operand), expr); if (result.castedArgTypes != null) { expr.operand = result.applyCastToArgs(List.of(expr.operand)).get(0); } @@ -4198,6 +4257,18 @@ private void visitSubCall(CallIndexExpr expr, Type typeBeforeSubCall) { var fieldType = Type.bool(); visitSliceIndexCall(expr, fieldType, subCall.argsIndices); type = expr.type; + } else if (type instanceof FloatStatusType) { + var allowedStatusfields = List.of("nv", "dz", "of", "uf", "nx"); + if (!allowedStatusfields.contains(fieldName)) { + var suggestions = Levenshtein.sortAll(fieldName, allowedStatusfields); + addErrorAndStopChecking( + error("Unknown float status field `%s`".formatted(fieldName), expr) + .suggestions(suggestions) + .build()); + } + var fieldType = Type.bool(); + visitSliceIndexCall(expr, fieldType, subCall.argsIndices); + type = expr.type; } else if (type instanceof InstructionType) { var allowedStatusfields = List.of("address", "read", "unknown", "compute", "verify", "write", "readOrForward", @@ -4305,6 +4376,9 @@ private void processCallOfBuiltIn(CallIndexExpr expr) { // Builtin function List args = !expr.argsIndices.isEmpty() ? expr.argsIndices.getFirst().values : new ArrayList<>(); + var symbolArgs = expr.symbolArgs(); + var constArgs = symbolArgs == null ? List.of() : symbolArgs; + checkExpressions(constArgs); var argTypes = checkExpressions(args); var builtin = AstUtils.getBuiltIn(expr.target.path().pathToString(), argTypes); @@ -4317,7 +4391,7 @@ private void processCallOfBuiltIn(CallIndexExpr expr) { expr.computedBuiltIn = builtin; - var checkResult = checkBuiltin(builtin, args, expr); + var checkResult = checkBuiltin(builtin, constArgs, args, expr); if (checkResult.castedArgTypes != null) { expr.replaceArgsFor(0, checkResult.applyCastToArgs(args)); } @@ -4451,8 +4525,18 @@ private void processCallOfTarget(CallIndexExpr expr, Node callTarget) { expr.typeBeforeSlice = typedNode.type(); } - var targetSizeExpr = expr.target.size(); - if (targetSizeExpr != null) { + var targetSymbolArgs = expr.target.symbolArgs(); + if (targetSymbolArgs != null) { + if (targetSymbolArgs.size() != 1) { + var err = error("Invalid scaling arguments", expr); + if (!targetSymbolArgs.isEmpty()) { + var loc = targetSymbolArgs.stream().map(WithLocation::location) + .reduce(SourceLocation::join).get(); + err = err.locationDescription(loc, "Expected exactly one scaling argument."); + } + throw addErrorAndStopChecking(err.build()); + } + var targetSizeExpr = targetSymbolArgs.getFirst(); if (!(expr.typeBeforeSlice() instanceof BitsType exprType)) { throw addErrorAndStopChecking(error("Invalid scaling type", targetSizeExpr) .locationDescription(targetSizeExpr, "Result type `%s` cannot be scaled.", diff --git a/vadl-frontend/main/vadl/ast/Ungrouper.java b/vadl-frontend/main/vadl/ast/Ungrouper.java index 21927451b..03315d767 100644 --- a/vadl-frontend/main/vadl/ast/Ungrouper.java +++ b/vadl-frontend/main/vadl/ast/Ungrouper.java @@ -63,6 +63,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -284,7 +285,7 @@ public Expr visit(CastExpr expr) { @Override public Expr visit(SymbolExpr expr) { - expr.size = expr.size.accept(this); + expr.symbolArgs.replaceAll(e -> e.accept(this)); return expr; } @@ -360,6 +361,12 @@ public Expr visit(ResourceReferenceExression expr) { return expr; } + @Override + public Void visit(FloatTypeDefinition definition) { + ungroupAnnotations(definition); + return null; + } + @Override public Void visit(ConstantDefinition definition) { ungroupAnnotations(definition); diff --git a/vadl-frontend/main/vadl/ast/ViamLowering.java b/vadl-frontend/main/vadl/ast/ViamLowering.java index 88e5efe88..b8861c13b 100644 --- a/vadl-frontend/main/vadl/ast/ViamLowering.java +++ b/vadl-frontend/main/vadl/ast/ViamLowering.java @@ -78,6 +78,7 @@ import vadl.ast.nodes.ExpandedAliasDefSequenceCallExpr; import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.FormatDefinition; import vadl.ast.nodes.FormatField; import vadl.ast.nodes.FunctionDefinition; @@ -141,6 +142,7 @@ import vadl.viam.Counter; import vadl.viam.Encoding; import vadl.viam.ExceptionDef; +import vadl.viam.FloatFormat; import vadl.viam.Format; import vadl.viam.Function; import vadl.viam.Group; @@ -1095,6 +1097,13 @@ public Optional visit(CacheDefinition definition) { definition.getClass().getSimpleName())); } + @Override + public Optional visit(FloatTypeDefinition definition) { + var identifier = + new vadl.viam.Identifier(definition.viamId, definition.identifier().location()); + return Optional.of(new FloatFormat(identifier)); + } + @Override public Optional visit(ConstantDefinition definition) { // Do nothing on purpose. @@ -1391,7 +1400,7 @@ private void setFieldAccessPredicate(PredicateFormatField predField) { } /** - * Get the field encoding for the {@link vadl.ast.EncodingFormatField} + * Get the field encoding for the {@link EncodingFormatField} * with the kind {@code ENCODING}. */ @SuppressWarnings("LineLength") @@ -1656,6 +1665,7 @@ private InstructionSetArchitecture visitAndMergeIsa(InstructionSetDefinition def mergedDef.definitions.stream().map(this::fetch).flatMap(Optional::stream) .toList(); var formats = filterAndCastToInstance(allDefinitions, Format.class); + var floatFormats = filterAndCastToInstance(allDefinitions, FloatFormat.class); var functions = filterAndCastToInstance(allDefinitions, Function.class); var operations = filterAndCastToInstance(allDefinitions, Operation.class); var relocations = filterAndCastToInstance(allDefinitions, Relocation.class); @@ -1700,6 +1710,7 @@ private InstructionSetArchitecture visitAndMergeIsa(InstructionSetDefinition def programCounter, memories, artificialResources, + floatFormats, group ); diff --git a/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java b/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java index 70615e3ac..3604a0a34 100644 --- a/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Objects; import java.util.function.Consumer; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import vadl.ast.TensorType; import vadl.types.BuiltInTable; @@ -193,8 +194,9 @@ public IsId path() { } @Override - public @Nullable Expr size() { - return target.size(); + @Nullable + public List symbolArgs() { + return target.symbolArgs(); } @Override diff --git a/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java b/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java index 042da34f8..fc38b13f4 100644 --- a/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java +++ b/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java @@ -68,6 +68,8 @@ public interface DefinitionVisitor { public R visit(ExceptionDefinition definition); + public R visit(FloatTypeDefinition definition); + public R visit(FormatDefinition definition); public R visit(DerivedFormatField definition); diff --git a/vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java b/vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java new file mode 100644 index 000000000..ec4a116a2 --- /dev/null +++ b/vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.ast.nodes; + +import java.util.Objects; +import javax.annotation.Nullable; +import vadl.types.Type; +import vadl.utils.SourceLocation; + +/** + * Represents a float-type definition, which is used to specify float formats. + *
{@code
+ * [ IEEE : 32 ]
+ * float-type binary32
+ * }
+ */ +@SuppressWarnings({"MissingJavadocType", "MissingJavadocMethod"}) +public class FloatTypeDefinition extends Definition implements IdentifiableNode, TypedNode { + public IdentifierOrPlaceholder identifier; + + /** + * Represents the bit-size of the represented float format. This is used by the type-checker + * to infer types when the float-type is used as a constant parameter in built-ins. This is not + * set during parsing, and must be set by an annotation such as {@code [ IEEE : 32 ]}. + */ + @Nullable + public Integer size; + + public SourceLocation loc; + + public FloatTypeDefinition(IdentifierOrPlaceholder identifier, SourceLocation loc) { + this.identifier = identifier; + this.loc = loc; + } + + @Override + public Identifier identifier() { + return (Identifier) identifier; + } + + @Override + public SourceLocation location() { + return loc; + } + + @Override + public SyntaxType syntaxType() { + return BasicSyntaxType.COMMON_DEFS; + } + + @Override + public void prettyPrint(int indent, StringBuilder builder) { + prettyPrintAnnotations(indent, builder); + builder.append(prettyIndentString(indent)); + builder.append("float-type %s".formatted(identifier().name)); + builder.append("\n"); + } + + @Override + public R accept(DefinitionVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + FloatTypeDefinition that = (FloatTypeDefinition) o; + return Objects.equals(identifier, that.identifier); + } + + @Override + public int hashCode() { + return Objects.hashCode(identifier); + } + + @Override + public Type type() { + return Type.floatType(); + } +} diff --git a/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java b/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java index 0afa56bde..10862b439 100644 --- a/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java @@ -16,6 +16,7 @@ package vadl.ast.nodes; +import java.util.List; import javax.annotation.Nullable; import vadl.utils.WithLocation; @@ -24,7 +25,7 @@ public sealed interface IsCallExpr extends WithLocation permits CallIndexExpr, I public IsId path(); @Nullable - public Expr size(); + public List symbolArgs(); public void prettyPrint(int indent, StringBuilder builder); } diff --git a/vadl-frontend/main/vadl/ast/nodes/IsId.java b/vadl-frontend/main/vadl/ast/nodes/IsId.java index d8dabab2e..178292015 100644 --- a/vadl-frontend/main/vadl/ast/nodes/IsId.java +++ b/vadl-frontend/main/vadl/ast/nodes/IsId.java @@ -16,6 +16,7 @@ package vadl.ast.nodes; +import java.util.List; import javax.annotation.Nullable; @SuppressWarnings({"MissingJavadocType", "MissingJavadocMethod"}) @@ -28,7 +29,8 @@ public default IsId path() { } @Override - public default @Nullable Expr size() { + @Nullable + public default List symbolArgs() { return null; } diff --git a/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java b/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java index 9366995a8..73e6410e5 100644 --- a/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java @@ -16,6 +16,7 @@ package vadl.ast.nodes; +import java.util.List; import javax.annotation.Nullable; @SuppressWarnings({"MissingJavadocType", "MissingJavadocMethod"}) @@ -25,5 +26,5 @@ public sealed interface IsSymExpr extends IsCallExpr permits SymbolExpr, IsId { @Override @Nullable - public Expr size(); + public List symbolArgs(); } diff --git a/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java b/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java index 0d5cfdfa3..98a52299e 100644 --- a/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java +++ b/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java @@ -247,6 +247,14 @@ public Void visit(ExceptionDefinition definition) { return null; } + @Override + public Void visit(FloatTypeDefinition definition) { + beforeTravel(definition); + definition.forEachChild(this::travel); + afterTravel(definition); + return null; + } + @Override public Void visit(FormatDefinition definition) { beforeTravel(definition); diff --git a/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java b/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java index b62dbda77..db8d03dd8 100644 --- a/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java @@ -16,24 +16,29 @@ package vadl.ast.nodes; +import java.util.List; import java.util.Objects; import vadl.javaannotations.ast.Child; import vadl.utils.SourceLocation; /** - * A representation of terms of form {@code "MEM<9>"}. + * A representation of terms of form {@code MEM<9>} or {@code VADL::fcvts::}. + * These terms always have at least one argument in the pointy brackets. */ @SuppressWarnings("MissingJavadocMethod") public final class SymbolExpr extends Expr implements IsSymExpr { @Child public IsId path; + /** + * The list of arguments in the pointy brackets. Always contains at least one element. + */ @Child - public Expr size; + public List symbolArgs; public SourceLocation location; - public SymbolExpr(IsId path, Expr size, SourceLocation location) { + public SymbolExpr(IsId path, List symbolArgs, SourceLocation location) { this.path = path; - this.size = size; + this.symbolArgs = symbolArgs; this.location = location; } @@ -43,8 +48,8 @@ public IsId path() { } @Override - public Expr size() { - return size; + public List symbolArgs() { + return symbolArgs; } @Override @@ -60,11 +65,25 @@ public SyntaxType syntaxType() { @Override public void prettyPrintExpr(int indent, StringBuilder builder, Precedence parentPrec) { path.prettyPrint(indent, builder); - var prefix = size instanceof BinaryExpr ? "<( " : "< "; - var suffix = size instanceof BinaryExpr ? " )>" : " >"; - builder.append(prefix); - size.prettyPrintExpr(indent, builder, Precedence.NoPrecedence); - builder.append(suffix); + if (symbolArgs.size() > 1) { + builder.append("::"); + } + builder.append("< "); + boolean first = true; + for (var arg : symbolArgs) { + if (!first) { + builder.append(", "); + } + if (arg instanceof BinaryExpr) { + builder.append("("); + } + arg.prettyPrintExpr(0, builder, Precedence.NoPrecedence); + if (arg instanceof BinaryExpr) { + builder.append(")"); + } + first = false; + } + builder.append(" >"); } @Override @@ -83,13 +102,13 @@ public boolean equals(Object o) { } SymbolExpr that = (SymbolExpr) o; - return path.equals(that.path) && Objects.equals(size, that.size); + return path.equals(that.path) && symbolArgs.equals(that.symbolArgs); } @Override public int hashCode() { int result = path.hashCode(); - result = 31 * result + Objects.hashCode(size); + result = 31 * result + Objects.hashCode(symbolArgs); return result; } } diff --git a/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java b/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java index 2c51afa4f..a208ad9d3 100644 --- a/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java +++ b/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java @@ -49,8 +49,8 @@ public TypeLiteral(IsId baseType, List sizeIndices, SourceLocation loc) { public TypeLiteral(IsSymExpr symExpr) { this.baseType = symExpr.path(); - var size = symExpr.size(); - this.sizeIndices = size == null ? List.of() : List.of(size); + var symbolArgs = symExpr.symbolArgs(); + this.sizeIndices = symbolArgs == null ? List.of() : List.of(symbolArgs.getFirst()); this.loc = symExpr.location(); } diff --git a/vadl-frontend/main/vadl/ast/vadl.ATG b/vadl-frontend/main/vadl/ast/vadl.ATG index 352afcbed..3eb746d75 100644 --- a/vadl-frontend/main/vadl/ast/vadl.ATG +++ b/vadl-frontend/main/vadl/ast/vadl.ATG @@ -181,6 +181,7 @@ TOKENS FALSE = "false". FETCH = "fetch". FILE = "file". + FLOAT_TYPE = "float-type". FOLD = "fold". FOR = "for". FORALL = "forall". @@ -417,6 +418,7 @@ The #commonDefinitionList production rule lists (beside macro definitions) all c commonDefinition (. def = DUMMY_DEF; .) = constantDefinition + | floatTypeDefinition | formatDefinition | enumerationDefinition | usingDefinition @@ -516,6 +518,11 @@ elements. A single annotation is described in the #annotation production rule. expression (. def = new ConstantDefinition(id, type, expr, startLocation.join(lastTokenLoc())); .) . + floatTypeDefinition (. var startLocation = nextTokenLoc(); .) + = FLOAT_TYPE + identifierOrPlaceholder (. def = new FloatTypeDefinition(id, startLocation.join(lastTokenLoc())); .) + . + formatDefinition (. var startLoc = nextTokenLoc(); var fields = new ArrayList(); .) = FORMAT identifierOrPlaceholder @@ -1797,15 +1804,30 @@ A micro architecture definition (#microArchitectureDefinition) is shown in line = IF (isIdentifierToken(la) || isMacroReplacementOfType(this, BasicSyntaxType.ID)) identifierPath [ - IF (la.kind == _SYM_LT) - SYM_LT (. var lessLoc = lastTokenLoc(); .) - term - [ - IF (!allowLtOp || la.kind == _SYM_GT) - SYM_GT (. expr = new SymbolExpr(path, term, path.location().join(lastTokenLoc())); .) - ] (. if (expr.equals(DUMMY_EXPR)) expr = new BinaryExpr((Expr) path, new BinOp(Operator.Less, lessLoc), term); .) - ] (. if (expr.equals(DUMMY_EXPR)) expr = (Expr) path; .) - | macroReplacement (. expr = castExpr(this, node); .) + // TODO: decide if the ::<...> syntax should have its own rule + // see Discussion #578, especially the comment + // https://github.com/OpenVADL/openvadl/discussions/578#discussioncomment-17872831 + IF (la.kind == _SYM_NAMESPACE || la.kind == _SYM_LT) (. var values = new ArrayList(); .) + ( + IF (la.kind == _SYM_NAMESPACE) + SYM_NAMESPACE + SYM_LT + term (. values.add(firstTerm); .) + { + SYM_COMMA + term (. values.add(term); .) + } + SYM_GT (. expr = new SymbolExpr(path, values, path.location().join(lastTokenLoc())); .) + | + SYM_LT (. var lessLoc = lastTokenLoc(); .) + term (. values.add(term); .) + [ + IF (!allowLtOp || la.kind == _SYM_GT) + SYM_GT (. expr = new SymbolExpr(path, values, path.location().join(lastTokenLoc())); .) + ] (. if (expr.equals(DUMMY_EXPR)) expr = new BinaryExpr((Expr) path, new BinOp(Operator.Less, lessLoc), term); .) + ) + ] (. if (expr.equals(DUMMY_EXPR)) expr = (Expr) path; .) + | macroReplacement (. expr = castExpr(this, node); .) . binaryOperator (. op = null; .) @@ -1917,6 +1939,7 @@ A micro architecture definition (#microArchitectureDefinition) is shown in line identifierPath (. List segments = new ArrayList<>(); .) = identifierOrPlaceholder (. segments.add(id); .) { + IF (la.kind == _SYM_NAMESPACE && scanner.Peek().kind != _SYM_LT) // symbol expression can look like id::path::<>() SYM_NAMESPACE identifierOrPlaceholder (. segments.add(next); .) } (. if (segments.size() == 1) path = id; diff --git a/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl b/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl index a6df3b0fb..b53590407 100644 --- a/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl +++ b/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl @@ -28,7 +28,7 @@ function abc(x: Bits<8>) -> Bits<8> = VADL::adds(1, x) // ╭── test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl:8:39 // │ // 8 │ function abc(x: Bits<8>) -> Bits<8> = VADL::adds(1, x) -// │ ^^^^^^^^^^^^^^^^ The builtin has the signature `(BitsType, BitsType) -> StructType` but got `Bits<1>, Bits<8>`. +// │ ^^^^^^^^^^^^^^^^ The builtin has the signature `(BitsType, BitsType) -> StructType` but got `(Bits<1>, Bits<8>)`. // │ help: Try casting some of the constant arguments to explicit types. // │ // diff --git a/vadl/main/vadl/dump/InfoUtils.java b/vadl/main/vadl/dump/InfoUtils.java index dc6610443..ceb8b6396 100644 --- a/vadl/main/vadl/dump/InfoUtils.java +++ b/vadl/main/vadl/dump/InfoUtils.java @@ -16,6 +16,7 @@ package vadl.dump; +import com.google.common.html.HtmlEscapers; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -230,7 +231,7 @@ public static Info.Expandable createCodeBlockExpandable(String title, """
%s
             
- """.formatted(code) + """.formatted(HtmlEscapers.htmlEscaper().escape(code)) ); } diff --git a/vadl/main/vadl/types/BuiltInTable.java b/vadl/main/vadl/types/BuiltInTable.java index 34f6e41e5..8278c14e8 100644 --- a/vadl/main/vadl/types/BuiltInTable.java +++ b/vadl/main/vadl/types/BuiltInTable.java @@ -16,6 +16,7 @@ package vadl.types; +import static java.util.Objects.requireNonNull; import static org.slf4j.LoggerFactory.getLogger; import static vadl.types.Type.constructDataType; @@ -1582,7 +1583,7 @@ public boolean hasSameBitWidth() { return hasSameBitWidth; } - public Optional compute(List args) { + public Optional compute(List constArgs, List args) { logger.atWarn().log("Computation of constants for built-in {} is not implemented", this); return Optional.empty(); } @@ -1600,10 +1601,11 @@ public boolean isStatusBuiltin() { * in the built-in definition, and if not, if it is possible to produce a type of the * parameter type class that can be trivially cast from the argument type. * - * @param argTypes of the concrete arguments + * @param constArgs the concrete constant arguments + * @param argTypes of the concrete arguments * @return true if argument types are correct, false otherwise */ - public boolean takes(List argTypes) { + public boolean takes(List constArgs, List argTypes) { if (this.signature().hasVarArgs()) { if (argTypes.size() < argTypeClasses().size()) { @@ -1629,7 +1631,13 @@ public boolean takes(List argTypes) { return false; } - return argsCompatible(argTypes, argTypeClasses()); + if (constArgTypeClasses().size() != constArgs.size()) { + // if the number of constant arguments is not correct, this can't be true + return false; + } + + return argsCompatible(argTypes, argTypeClasses()) + && argsCompatible(constArgs.stream().map(Constant::type).toList(), constArgTypeClasses()); } private boolean argsCompatible(List argTypes, @@ -1666,15 +1674,29 @@ private boolean argsCompatible(List argTypes, return true; } + /** + * Returns the result type of the built-in when called with the given argument types and + * constant arguments. + * It assumes that the argument types are valid, such as a call to + * {@link #takes(List, List)} would return true. + * + * @param constArgs concrete constant arguments. + * @param argTypes concrete types of argument for call. + * @return the concrete type that is return by this built-in + */ + public abstract Type returns(List constArgs, List argTypes); + /** * Returns the result type of the built-in when called with the given argument types. * It assumes that the argument types are valid, such as a call to - * {@link #takes(List)} would return true. + * {@link #takes(List, List)} would return true. * * @param argTypes concrete types of argument for call. * @return the concrete type that is return by this built-in */ - public abstract Type returns(List argTypes); + public Type returns(List argTypes) { + return returns(List.of(), argTypes); + } public final boolean matches(RelationType type) { @@ -1686,6 +1708,10 @@ public String toString() { return name + signature; } + public List> constArgTypeClasses() { + return signature.constArgTypeClass(); + } + public List> argTypeClasses() { return signature.argTypeClasses(); } @@ -1746,11 +1772,11 @@ public static class BuiltInBuilder { private RelationType signature; private BuiltIn.Kind kind; @Nullable - private Function, Optional> computeFunction; + private BiFunction, List, Optional> computeFunction; @Nullable - private Function, Boolean> takesFunction; + private BiFunction, List, Boolean> takesFunction; @Nullable - private Function, Type> returnsFunction; + private BiFunction, List, Type> returnsFunction; private boolean hasSameBitWidth = false; BuiltInBuilder(String name, @Nullable String operator, RelationType signature, @@ -1763,52 +1789,74 @@ public static class BuiltInBuilder { public BuiltInBuilder computeUnary( Function computeFunction) { - this.computeFunction = - (args) -> Optional.of(computeFunction.apply((T) args.get(0))); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply((T) args.get(0))); return this; } public BuiltInBuilder compute( Function, R> computeFunction) { - this.computeFunction = - (args) -> Optional.of(computeFunction.apply(args.stream().map(a -> (T) a).toList())); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply(args.stream().map(a -> (T) a).toList())); return this; } public BuiltInBuilder compute( BiFunction computeFunction) { - this.computeFunction = - (args) -> Optional.of(computeFunction.apply((A) args.get(0), (B) args.get(1))); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply((A) args.get(0), (B) args.get(1))); return this; } @SuppressWarnings("LineLength") public BuiltInBuilder compute( TriFunction computeFunction) { - this.computeFunction = - (args) -> Optional.of( - computeFunction.apply((A) args.get(0), (B) args.get(1), (C) args.get(2))); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply((A) args.get(0), (B) args.get(1), (C) args.get(2))); return this; } public BuiltInBuilder noCompute() { - this.computeFunction = (args) -> Optional.empty(); + this.computeFunction = (constArgs, args) -> Optional.empty(); return this; } - public BuiltInBuilder takesData(Function, Boolean> takesFunction) { - this.takesFunction = (args) -> args.stream().allMatch(DataType.class::isInstance) - && takesFunction.apply(args.stream().map(DataType.class::cast).toList()); + public BuiltInBuilder takesData( + BiFunction, List, Boolean> takesFunction) { + this.takesFunction = (constArgs, args) -> args.stream().allMatch(DataType.class::isInstance) + && takesFunction.apply(constArgs, args.stream().map(DataType.class::cast).toList()); return this; } + public BuiltInBuilder takesData(Function, Boolean> takesFunction) { + return takesData((constArgs, args) -> takesFunction.apply(args)); + } + + public BuiltInBuilder takesDataFromFirstFloatSize( + BiFunction, Boolean> takesFunction) { + return takesData((constArgs, args) -> { + ensure(!constArgs.isEmpty(), "Expected at least one constant argument, but found none."); + return takesFunction.apply(floatTypeSize(constArgs.get(0)), args); + }); + } + + public BuiltInBuilder takesDataFromFirstTwoFloatSizes( + TriFunction, Boolean> takesFunction) { + return takesData((constArgs, args) -> { + ensure(constArgs.size() >= 2, "Expected at least two constant argument, but found %d.", + constArgs.size()); + return takesFunction.apply( + floatTypeSize(constArgs.get(0)), floatTypeSize(constArgs.get(1)), args); + }); + } + /** - * This will use the default implementation of {@link BuiltIn#takes(List)}. + * This will use the default implementation of {@link BuiltIn#takes(List, List)}. * So it will compare type classes and checks if an argument is trivially cast * to a parameter's type class. */ public BuiltInBuilder takesDefault() { - this.takesFunction = (args) -> true; + this.takesFunction = (constArgs, args) -> true; return this; } @@ -1826,12 +1874,32 @@ public BuiltInBuilder takesFirstTwoWithSameBitWidths() { return this; } + public BuiltInBuilder takesFloatArgs(int floatArgCount) { + return takesDataFromFirstFloatSize((size, args) -> args.size() == floatArgCount + && args.stream().limit(floatArgCount).allMatch(a -> a.bitWidth() == size)); + } + + public BuiltInBuilder takesFloatArgsAndFrm(int floatArgCount) { + return takesDataFromFirstFloatSize((size, args) -> args.size() == floatArgCount + 1 + && args.stream().limit(floatArgCount).allMatch(a -> a.bitWidth() == size) + && args.get(floatArgCount).bitWidth() == 3); + } + + public BuiltInBuilder takesFrm(int frmArgIdx) { + return takesData(args -> args.size() == frmArgIdx + 1 && args.get(frmArgIdx).bitWidth() == 3); + } + public BuiltInBuilder returns(Type returnType) { returns((args) -> returnType); return this; } public BuiltInBuilder returns(Function, Type> returnsFunction) { + returns((constArgs, args) -> returnsFunction.apply(args)); + return this; + } + + public BuiltInBuilder returns(BiFunction, List, Type> returnsFunction) { this.returnsFunction = returnsFunction; return this; } @@ -1840,17 +1908,25 @@ public BuiltInBuilder returnsFirstBitWidth(Class returnT returnsFromFirstAsDataType( (firstDataType) -> { var result = constructDataType(returnTypeClass, firstDataType.bitWidth()); - Objects.requireNonNull(result); + requireNonNull(result); return result; }); return this; } + public BuiltInBuilder returnsFirstFloatType() { + return returnsFromFirstFloatSize(BitsType::bits); + } + + public BuiltInBuilder returnsSecondFloatType() { + return returnsFromFirstTwoFloatSizes((s0, s1) -> BitsType.bits(s1)); + } + public BuiltInBuilder returnsFirstBitWidthAndStatus( Class returnTypeClass) { returnsFromFirstAsDataType((firstDataType) -> { var valType = constructDataType(returnTypeClass, firstDataType.bitWidth()); - Objects.requireNonNull(valType); + requireNonNull(valType); return Type.struct( BUILTIN_RESULT, valType, BUILTIN_STATUS, Type.status() @@ -1879,6 +1955,42 @@ public BuiltInBuilder returnsFromDataTypes(Function, Type> return return this; } + public BuiltInBuilder returnsFromSecondConstSize( + Class returnTypeClass) { + return returns((constArgs, args) -> { + ensure(constArgs.size() >= 2, "Expected at least two constant argument, but found %d.", + constArgs.size()); + return requireNonNull(constructDataType(returnTypeClass, constInt(constArgs.get(1)))); + }); + } + + public BuiltInBuilder returnsFromFirstFloatSize(Function returnFunction) { + return returns((constArgs, args) -> { + ensure(!constArgs.isEmpty(), "Expected at least one constant argument, but found none."); + return returnFunction.apply(floatTypeSize(constArgs.get(0))); + }); + } + + public BuiltInBuilder returnsFromFirstTwoFloatSizes( + BiFunction returnFunction) { + return returns((constArgs, args) -> { + ensure(constArgs.size() >= 2, "Expected at least two constant argument, but found %d.", + constArgs.size()); + return returnFunction.apply( + floatTypeSize(constArgs.get(0)), floatTypeSize(constArgs.get(1))); + }); + } + + private int floatTypeSize(Constant arg) { + ensure(arg instanceof Constant.FloatType, "Expected a float type, but found %s", arg); + return ((Constant.FloatType) arg).size(); + } + + private int constInt(Constant arg) { + ensure(arg instanceof Constant.Value, "Expected a value type, but found %s", arg); + return ((Constant.Value) arg).intValue(); + } + public BuiltIn build() { @@ -1894,39 +2006,37 @@ public BuiltIn build() { return new BuiltIn(name, operator, signature, kind, hasSameBitWidth) { @Override - public Optional compute(List args) { + public Optional compute(List constArgs, List args) { if (computeFunction == null) { - return super.compute(args); + return super.compute(constArgs, args); } - var argTypes = args.stream() - .map(Constant::type) - .toList(); - if (!takes(argTypes)) { + var argTypes = args.stream().map(Constant::type).toList(); + if (!takes(constArgs, argTypes)) { throw new ViamError("Types of arguments does not match type signature of " + signature) .addContext("built-in", this) - .addContext("constants", List.of(args)); + .addContext("constants", List.of(constArgs, args)); } - return computeFunction.apply(args) + return computeFunction.apply(constArgs, args) .map(result -> result instanceof Constant.Value value - ? value.trivialCastTo(returns(argTypes)) + ? value.trivialCastTo(returns(constArgs, argTypes)) : result); } @Override - public boolean takes(List argTypes) { + public boolean takes(List constArgs, List argTypes) { // always check general case first - var generalConstraintsValid = super.takes(argTypes); + var generalConstraintsValid = super.takes(constArgs, argTypes); if (generalConstraintsValid) { // if general case doesn't fail, then test specific constraints - return takesFunction.apply(argTypes); + return takesFunction.apply(constArgs, argTypes); } return false; } @Override - public Type returns(List argTypes) { - return returnsFunction.apply(argTypes); + public Type returns(List constArgs, List argTypes) { + return returnsFunction.apply(constArgs, argTypes); } }; } diff --git a/vadl/main/vadl/types/FloatStatusType.java b/vadl/main/vadl/types/FloatStatusType.java new file mode 100644 index 000000000..324f75701 --- /dev/null +++ b/vadl/main/vadl/types/FloatStatusType.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.types; + +/** + * A class that represents the VADL float status type. + * + *

It is actually a struct of with five boolean fields. + * These five elements represent status flags: + *

  • nv
  • + *
  • dz
  • + *
  • of
  • + *
  • uf
  • + *
  • nx
  • + * in that order. + */ +public class FloatStatusType extends StructType { + + public static final String INVALID = "nv"; + public static final String DIVISION_BY_ZERO = "dz"; + public static final String OVERFLOW = "of"; + public static final String UNDERFLOW = "uf"; + public static final String INEXACT = "nx"; + + protected FloatStatusType() { + super(Type.struct( + INVALID, Type.bool(), + DIVISION_BY_ZERO, Type.bool(), + OVERFLOW, Type.bool(), + UNDERFLOW, Type.bool(), + INEXACT, Type.bool() + ).fields()); + } + + @Override + public String name() { + return "FloatStatus"; + } + +} diff --git a/vadl/main/vadl/types/FloatType.java b/vadl/main/vadl/types/FloatType.java new file mode 100644 index 000000000..d903ae18b --- /dev/null +++ b/vadl/main/vadl/types/FloatType.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.types; + +/** + * A class that represents the VADL float type. + */ +public class FloatType extends Type { + + protected FloatType() { } + + @Override + public String name() { + return "FloatType"; + } +} diff --git a/vadl/main/vadl/types/RelationType.java b/vadl/main/vadl/types/RelationType.java index 9585d6a3c..d6bac31ed 100644 --- a/vadl/main/vadl/types/RelationType.java +++ b/vadl/main/vadl/types/RelationType.java @@ -28,12 +28,15 @@ public class RelationType extends Type { private final List> argTypeClass; + private final List> constArgTypeClass; private final boolean hasVarArgs; private final Class resultTypeClass; - protected RelationType(List> argTypes, boolean hasVarArgs, + protected RelationType(List> argTypes, + List> constArgTypeClass, boolean hasVarArgs, Class resultType) { this.argTypeClass = argTypes; + this.constArgTypeClass = constArgTypeClass; this.hasVarArgs = hasVarArgs; this.resultTypeClass = resultType; } @@ -42,6 +45,10 @@ public List> argTypeClasses() { return argTypeClass; } + public List> constArgTypeClass() { + return constArgTypeClass; + } + public boolean hasVarArgs() { return hasVarArgs; } @@ -52,10 +59,18 @@ public Class resultTypeClass() { @Override public String name() { - return "(" - + argTypeClass.stream().map(Class::getSimpleName) - .collect(Collectors.joining(", ")) - + ") -> " - + resultTypeClass.getSimpleName(); + var sb = new StringBuilder(); + if (!constArgTypeClass.isEmpty()) { + sb.append("<"); + sb.append(constArgTypeClass.stream().map(Class::getSimpleName) + .collect(Collectors.joining(", "))); + sb.append(">"); + } + sb.append("("); + sb.append(argTypeClass.stream().map(Class::getSimpleName) + .collect(Collectors.joining(", "))); + sb.append(") -> "); + sb.append(resultTypeClass.getSimpleName()); + return sb.toString(); } } diff --git a/vadl/main/vadl/types/Type.java b/vadl/main/vadl/types/Type.java index 225bac78c..3ca3b04cc 100644 --- a/vadl/main/vadl/types/Type.java +++ b/vadl/main/vadl/types/Type.java @@ -120,6 +120,20 @@ public static UIntType unsignedInt(int bitWidth) { .computeIfAbsent(bitWidth, k -> new UIntType(bitWidth)); } + private static @Nullable FloatType floatType = null; + + /** + * Retrieves the instance of FloatType. + * + * @return the FloatType object + */ + public static FloatType floatType() { + if (floatType == null) { + floatType = new FloatType(); + } + return floatType; + } + /** * Returns a DummyType object. * @@ -188,6 +202,25 @@ public static StructType struct(String field1, Type type1, return struct(fields); } + /** + * Retrieves the struct type with the specified subtypes. + * + * @return the struct type with the specified subtypes + */ + public static StructType struct(String field1, Type type1, + String field2, Type type2, + String field3, Type type3, + String field4, Type type4, + String field5, Type type5) { + var fields = new LinkedHashMap(); + fields.put(field1, type1); + fields.put(field2, type2); + fields.put(field3, type3); + fields.put(field4, type4); + fields.put(field5, type5); + return struct(fields); + } + private static @Nullable StatusType statusType = null; /** @@ -202,6 +235,20 @@ public static StatusType status() { return statusType; } + private static @Nullable FloatStatusType floatStatusType = null; + + /** + * Retrieves the float status type instance. + * + * @return the float status type instance + */ + public static FloatStatusType floatStatus() { + if (floatStatusType == null) { + floatStatusType = new FloatStatusType(); + } + return floatStatusType; + } + private static @Nullable VoidType voidType = null; @@ -239,23 +286,53 @@ public static StringType string() { */ public static RelationType relation(List> argTypes, Class returnType) { - return relation(argTypes, false, returnType); + return relation(argTypes, List.of(), false, returnType); } /** * Retrieves the generic relation type. * - * @param argTypes the list of argument type classes - * @param hasVarArgs the flag indicating if the last argument of kind varargs - * @param returnType the return type class + * @param argTypes the list of argument type classes + * @param constArgTypes the list of constant argument type classes + * @param returnType the return type class + * @return the RelationType instance + */ + public static RelationType relation(List> argTypes, + List> constArgTypes, + Class returnType) { + return relation(argTypes, constArgTypes, false, returnType); + } + + /** + * Retrieves the generic relation type. + * + * @param argTypes the list of argument type classes + * @param hasVarArgs the flag indicating if the last argument of kind varargs + * @param returnType the return type class + * @return the RelationType instance + */ + public static RelationType relation(List> argTypes, + boolean hasVarArgs, + Class returnType) { + return relation(argTypes, List.of(), hasVarArgs, returnType); + } + + /** + * Retrieves the generic relation type. + * + * @param argTypes the list of argument type classes + * @param constArgTypes the list of constant argument type classes + * @param hasVarArgs the flag indicating if the last argument of kind varargs + * @param returnType the return type class * @return the RelationType instance */ public static RelationType relation(List> argTypes, + List> constArgTypes, boolean hasVarArgs, Class returnType) { - var hashCode = Objects.hash(argTypes, hasVarArgs, returnType); - return relationTypes - .computeIfAbsent(hashCode, k -> new RelationType(argTypes, hasVarArgs, returnType)); + var hashCode = Objects.hash(argTypes, constArgTypes, hasVarArgs, returnType); + return relationTypes.computeIfAbsent(hashCode, k -> + new RelationType(argTypes, constArgTypes, hasVarArgs, returnType)); } /** @@ -265,7 +342,7 @@ public static RelationType relation(List> argTypes, * @return the RelationType instance */ public static RelationType relation(Class returnType) { - return relation(List.of(), false, returnType); + return relation(List.of(), List.of(), false, returnType); } /** @@ -291,7 +368,7 @@ public static RelationType relation(Class argType, public static RelationType relation(Class firstArg, Class secondArg, Class returnType) { - return relation(List.of(firstArg, secondArg), false, returnType); + return relation(List.of(firstArg, secondArg), List.of(), false, returnType); } private static final HashMap concreteRelationTypes = @@ -403,6 +480,7 @@ public static GroupType group(Type elementType, UIntType lengthType, UIntType bi /// These are all the builtin types that exist in the language. /// Some of them cannot be initialized like them since they require a size, like `SInt<16>` /// which is why they are named bases. - public static final Set builtinTypeBases = - Set.of("Bool", "String", "Bits", "UInt", "SInt", "Instruction", "FetchResult"); + public static final Set builtinTypeBases = Set.of( + "Bool", "String", "Bits", "UInt", "SInt", "Instruction", "FetchResult" + ); } diff --git a/vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java b/vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java new file mode 100644 index 000000000..45c674e29 --- /dev/null +++ b/vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java @@ -0,0 +1,51 @@ +package vadl.utils.functionInterfaces; + +import java.util.Objects; + +/** + * Represents an operation that accepts four input arguments and returns no result. + * This is the four-arity specialization of {@code Consumer}. + * Unlike most other functional interfaces, {@code QuadConsumer} is expected + * to operate via side effects. + * + * @param the type of the first argument to the operation + * @param the type of the second argument to the operation + * @param the type of the third argument to the operation + * @param the type of the fourth argument to the operation + * @see java.util.function.Consumer + * @see java.util.function.BiConsumer + */ +@FunctionalInterface +public interface QuadConsumer { + + /** + * Performs this operation on the given arguments. + * + * @param t the first input argument + * @param u the second input argument + * @param v the third input argument + * @param w the fourth input argument + */ + void accept(T t, U u, V v, W w); + + /** + * Returns a composed {@code QuadConsumer} that performs, in sequence, this + * operation followed by the {@code after} operation. If performing either + * operation throws an exception, it is relayed to the caller of the + * composed operation. If performing this operation throws an exception, + * the {@code after} operation will not be performed. + * + * @param after the operation to perform after this operation + * @return a composed {@code QuadConsumer} that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default QuadConsumer andThen( + QuadConsumer after) { + Objects.requireNonNull(after); + return (t, u, v, w) -> { + accept(t, u, v, w); + after.accept(t, u, v, w); + }; + } +} \ No newline at end of file diff --git a/vadl/main/vadl/viam/Constant.java b/vadl/main/vadl/viam/Constant.java index 38a33828f..65071273a 100644 --- a/vadl/main/vadl/viam/Constant.java +++ b/vadl/main/vadl/viam/Constant.java @@ -16,6 +16,7 @@ package vadl.viam; +import static java.util.Objects.requireNonNull; import static vadl.error.Diagnostic.warning; import static vadl.types.BuiltInTable.BUILTIN_RESULT; import static vadl.types.BuiltInTable.BUILTIN_STATUS; @@ -42,6 +43,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.jetbrains.annotations.Contract; import vadl.error.DeferredDiagnosticStore; import vadl.types.BitsType; @@ -443,7 +445,7 @@ public Constant.Value multiply(Constant.Value other, boolean longVersion, boolea .multiply(b.integer()); // multiply with other value var newType = Type.constructDataType(divType.getClass(), 2 * divType.bitWidth()); - Objects.requireNonNull(newType); + requireNonNull(newType); return fromInteger(newValue, newType); } else { @@ -467,7 +469,7 @@ public Constant.Value divide(Constant.Value other, boolean signed) { var divType = signed ? Type.signedInt(type().bitWidth()) : Type.unsignedInt(type().bitWidth()); - Objects.requireNonNull(divType); + requireNonNull(divType); var a = this.trivialCastTo(divType); var b = other.trivialCastTo(divType); @@ -510,7 +512,7 @@ public Constant.Value modulo(Constant.Value other, boolean signed) { var divType = signed ? Type.signedInt(type().bitWidth()) : Type.unsignedInt(type().bitWidth()); - Objects.requireNonNull(divType); + requireNonNull(divType); var a = this.trivialCastTo(divType); var b = other.trivialCastTo(divType); @@ -1412,7 +1414,7 @@ public Map values() { * @return The Constant value at the specified name. */ public Constant get(String name) { - return Objects.requireNonNull(values.get(name), + return requireNonNull(values.get(name), "Struct does not contain a value with name %s".formatted(name)); } @@ -1551,6 +1553,82 @@ public Constant.Value overflow() { } } + /** + * Represents a constant float-type. + * + *

    It stores a reference to the {@link FloatFormat}. + */ + public static class FloatType extends Constant { + + @Nullable + private final FloatFormat format; + + private final Integer size; + private final String name; + + /** + * Constructs a float-type constant from a float format definition. + * + * @param format The float format definition. + */ + public FloatType(FloatFormat format) { + super(Type.floatType()); + this.format = format; + // the type-checker checks that float-type definitions have a size, and thus an encoding + this.size = requireNonNull(format.encoding()).size; + this.name = format.simpleName(); + } + + /** + * Constructs a dummy float-type constant from a float format encoding size. This is used by + * the type checker when the float format definition is not yet available. + * + * @param size The float format encoding size, i.e. the bit-size of the float type. + * @param name The name of the float format. + */ + public FloatType(int size, String name) { + super(Type.floatType()); + this.format = null; + this.size = size; + this.name = name; + } + + @Nullable + public FloatFormat format() { + return format; + } + + public Integer size() { + return size; + } + + @Override + public java.lang.String toString() { + return name + ": " + type().toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + FloatType floatType = (FloatType) o; + return Objects.equals(format, floatType.format) && Objects.equals(size, + floatType.size) && Objects.equals(name, floatType.name); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), format, size, name); + } + } + // HELPER FUNCTIONS diff --git a/vadl/main/vadl/viam/DefinitionVisitor.java b/vadl/main/vadl/viam/DefinitionVisitor.java index b5d0a6755..790b74af2 100644 --- a/vadl/main/vadl/viam/DefinitionVisitor.java +++ b/vadl/main/vadl/viam/DefinitionVisitor.java @@ -44,6 +44,8 @@ public interface DefinitionVisitor { void visit(Group group); + void visit(FloatFormat floatFormat); + void visit(Format format); void visit(Format.Field formatField); @@ -138,6 +140,7 @@ public void visit(InstructionSetArchitecture isa) { isa.ownMemories().forEach(e -> e.accept(this)); isa.artificialResources().forEach(e -> e.accept(this)); isa.ownInstructions().forEach(e -> e.accept(this)); + isa.ownFloatFormats().forEach(e -> e.accept(this)); isa.ownPseudoInstructions().forEach(e -> e.accept(this)); var pc = isa.pc(); if (pc != null) { @@ -198,6 +201,12 @@ public void visit(Group group) { afterTraversal(group); } + @Override + public void visit(FloatFormat floatFormat) { + beforeTraversal(floatFormat); + afterTraversal(floatFormat); + } + @Override public void visit(Format format) { beforeTraversal(format); @@ -483,6 +492,11 @@ public void visit(Group group) { } + @Override + public void visit(FloatFormat floatFormat) { + + } + @Override public void visit(Format format) { diff --git a/vadl/main/vadl/viam/FloatExceptionFlag.java b/vadl/main/vadl/viam/FloatExceptionFlag.java new file mode 100644 index 000000000..87e463727 --- /dev/null +++ b/vadl/main/vadl/viam/FloatExceptionFlag.java @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam; + +import java.util.Arrays; +import java.util.Optional; + +/** + * Enum representing float exception flags. + * + *

    The supported flags are: + *

      + *
    • INVALID
    • + *
    • DIV_BY_ZERO
    • + *
    • OVERFLOW
    • + *
    • UNDERFLOW
    • + *
    • INEXACT
    • + *
    + */ +public enum FloatExceptionFlag { + INVALID("invalid", 0), + DIV_BY_ZERO("div_by_zero", 1), + OVERFLOW("overflow", 2), + UNDERFLOW("underflow", 3), + INEXACT("inexact", 4); + + public final String name; + + /** + * See softfloat-types.h + */ + public final int qemuFlagOffset; + + FloatExceptionFlag(String name, int qemuFlagOffset) { + this.name = name; + this.qemuFlagOffset = qemuFlagOffset; + } + + public static Optional from(String name) { + return Arrays.stream(values()).filter(f -> f.name.equals(name)).findFirst(); + } +} diff --git a/vadl/main/vadl/viam/FloatFormat.java b/vadl/main/vadl/viam/FloatFormat.java new file mode 100644 index 000000000..410a5ced8 --- /dev/null +++ b/vadl/main/vadl/viam/FloatFormat.java @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import vadl.types.Type; + +/** + * Describes a float format. This covers bit-size, encoding and interpretation. + */ +public class FloatFormat extends Definition implements DefProp.WithType { + + /** + * Represents all supported float encodings. + */ + public enum Encoding { + IEEE32(32, true), + IEEE64(64, true); + + public final int size; + public final boolean ieee; + + Encoding(int size, boolean ieee) { + this.size = size; + this.ieee = ieee; + } + + /** + * Returns the IEEE encoding for the given bit-size. + */ + public static @Nullable Encoding ieee(int size) { + return switch (size) { + case 32 -> IEEE32; + case 64 -> IEEE64; + default -> null; + }; + } + } + + @Nullable + private Encoding encoding = null; + + public FloatFormat(Identifier identifier) { + super(identifier); + } + + public void setEncoding(@CheckForNull Encoding encoding) { + this.encoding = encoding; + } + + /** + * The encoding of the float format. + */ + @Nullable + public Encoding encoding() { + return encoding; + } + + /** + * The name of the float format in lower case. + */ + public String nameLower() { + return simpleName().toLowerCase(); + } + + @Override + public Type type() { + return Type.floatType(); + } + + @Override + public void accept(DefinitionVisitor visitor) { + visitor.visit(this); + } + + @Override + public void verify() { + super.verify(); + // FIXME: for now this is checked here, but this should create a diagnostic instead of ViamError + ensure(encoding != null, "Encoding not specified"); + } + + @Override + public String toString() { + return identifier.simpleName(); + } +} diff --git a/vadl/main/vadl/viam/InstructionSetArchitecture.java b/vadl/main/vadl/viam/InstructionSetArchitecture.java index c9fa2130e..58deeb422 100644 --- a/vadl/main/vadl/viam/InstructionSetArchitecture.java +++ b/vadl/main/vadl/viam/InstructionSetArchitecture.java @@ -34,6 +34,7 @@ public class InstructionSetArchitecture extends Definition { @Nullable private final Counter pc; + private final List floatFormats; private final List formats; private final List functions; private final List operations; @@ -72,6 +73,7 @@ public InstructionSetArchitecture(Identifier identifier, @Nullable Counter pc, List memories, List artificialResources, + List floatFormats, @Nullable Group group ) { super(identifier); @@ -87,6 +89,7 @@ public InstructionSetArchitecture(Identifier identifier, this.pc = pc; this.memories = memories; this.artificialResources = artificialResources; + this.floatFormats = floatFormats; this.group = group; // set parent architecture of instructions @@ -171,6 +174,14 @@ public List registerTensors() { } + /** + * Returns the {@link FloatFormat}s owned by this ISA. + * So it might not include definitions accessible through the super ISA. + */ + public List ownFloatFormats() { + return floatFormats; + } + /** * Returns the {@link Format}s owned by this ISA. * So it might not include definitions accessible through the super ISA. diff --git a/vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java b/vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java new file mode 100644 index 000000000..bf543cf54 --- /dev/null +++ b/vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.annotations; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import vadl.viam.Annotation; +import vadl.viam.FloatExceptionFlag; +import vadl.viam.RegisterTensor; + +/** + * Annotation for registers that store float exception flags. + * + *

    Maps each bit index of the register to either a {@link FloatExceptionFlag}, which + * can be either sticky or non-sticky, or {@code null}. + */ +public class FloatFlagAnnotation extends Annotation { + + private final Map sticky = new HashMap<>(); + private final Map nonSticky = new HashMap<>(); + + @Override + public Class parentDefinitionClass() { + return RegisterTensor.class; + } + + public boolean isSticky(int index) { + return sticky.containsKey(index); + } + + @Nullable + public FloatExceptionFlag get(int index) { + return isSticky(index) ? sticky.get(index) : nonSticky.get(index); + } + + public void set(int index, boolean isSticky, FloatExceptionFlag flag) { + (isSticky ? sticky : nonSticky).put(index, flag); + } + + public Map stickyFlags() { + return sticky; + } + + public Map nonStickyFlags() { + return nonSticky; + } + +} diff --git a/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java b/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java index 187f80794..d09542f83 100644 --- a/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java +++ b/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java @@ -23,6 +23,7 @@ import vadl.types.BuiltInTable; import vadl.types.BuiltInTable.BuiltIn; import vadl.types.Type; +import vadl.viam.Constant; import vadl.viam.graph.Canonicalizable; import vadl.viam.graph.GraphNodeVisitor; import vadl.viam.graph.Node; @@ -40,9 +41,19 @@ public class BuiltInCall extends AbstractFunctionCallNode implements Canonicaliz @DataValue protected BuiltIn builtIn; - public BuiltInCall(BuiltIn builtIn, NodeList args, Type type) { + @DataValue + protected List constArgs; + + @SuppressWarnings("checkstyle:MissingJavadocMethod") + public BuiltInCall(BuiltIn builtIn, List constArgs, + NodeList args, Type type) { super(args, type); this.builtIn = builtIn; + this.constArgs = constArgs; + } + + public BuiltInCall(BuiltIn builtIn, NodeList args, Type type) { + this(builtIn, List.of(), args, type); } /** @@ -78,6 +89,17 @@ public BuiltIn builtIn() { return this.builtIn; } + /** + * Gets the constant args, i.e. the args in the pointy brackets {@code <...>}. + */ + public List constArgs() { + return constArgs; + } + + public void setConstArgs(List constArgs) { + this.constArgs = constArgs; + } + public ExpressionNode arg(int index) { return args.get(index); } @@ -96,7 +118,7 @@ public Node canonical() { .toList(); return builtIn - .compute(args) + .compute(constArgs, args) .map(e -> (Node) new ConstantNode(e)) .orElse(this); } @@ -130,11 +152,11 @@ public void verifyState() { "Number of arguments must match, %s vs %s", argTypeClasses.size(), this.arguments().size()); var actualArgTypes = this.arguments().stream().map(ExpressionNode::type).toList(); - ensure(builtIn.takes(actualArgTypes), - "Arguments' types do not match with the type of the builtin. Args: %s", - actualArgTypes); + ensure(builtIn.takes(constArgs, actualArgTypes), + "Arguments' types do not match with the type of the builtin. Const args: %s, Args: %s", + constArgs, actualArgTypes); - var builtInResultType = builtIn.returns(actualArgTypes); + var builtInResultType = builtIn.returns(constArgs, actualArgTypes); ensure(builtInResultType.isTrivialCastTo(this.type()), "BuiltIns' result type does not match node's type. %s vs %s", builtInResultType, this.type() ); @@ -143,13 +165,14 @@ public void verifyState() { @Override public ExpressionNode copy() { return new BuiltInCall(builtIn, + constArgs.stream().toList(), new NodeList<>(this.arguments().stream().map(x -> (ExpressionNode) x.copy()).toList()), this.type()); } @Override public Node shallowCopy() { - return new BuiltInCall(builtIn, args, type()); + return new BuiltInCall(builtIn, constArgs, args, type()); } @@ -157,6 +180,7 @@ public Node shallowCopy() { protected void collectData(List collection) { super.collectData(collection); collection.add(builtIn); + collection.add(constArgs); } @Override @@ -169,6 +193,16 @@ public void prettyPrint(StringBuilder sb) { sb.append(")"); } else { sb.append(builtIn.name()); + if (!constArgs.isEmpty()) { + sb.append("::<"); + for (int i = 0; i < constArgs.size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(constArgs.get(i).toString()); + } + sb.append(">"); + } sb.append("("); for (int i = 0; i < args.size(); i++) { diff --git a/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java b/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java index 925c1a31d..4cc898f56 100644 --- a/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java +++ b/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java @@ -176,6 +176,7 @@ private static Fixture createTensorWriteFixture(String name, null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), null )); diff --git a/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java b/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java index a30aa4b09..084e5cf6a 100644 --- a/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java +++ b/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java @@ -95,6 +95,7 @@ void shouldReplaceAdditionWithConstant() { null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), null );