diff --git a/Sources/AST/AST.swift b/Sources/AST/AST.swift index dc188be5..36355994 100644 --- a/Sources/AST/AST.swift +++ b/Sources/AST/AST.swift @@ -200,13 +200,6 @@ public struct FunctionDeclaration: SourceEntity { public var explicitParameters: [Parameter] { return parameters.filter { !$0.isImplicit } } - - /// The parameters of the function, as variable declaration values. - public var parametersAsVariableDeclarations: [VariableDeclaration] { - return parameters.map { parameter in - return VariableDeclaration(declarationToken: nil, identifier: parameter.identifier, type: parameter.type) - } - } public var mutatingToken: Token { return modifiers.first { $0.kind == .mutating }! @@ -215,8 +208,11 @@ public struct FunctionDeclaration: SourceEntity { public var isPublic: Bool { return hasModifier(kind: .public) } + + // Contextual information for the scope defined by the function. + public var scopeContext: ScopeContext? = nil - public init(funcToken: Token, attributes: [Attribute], modifiers: [Token], identifier: Identifier, parameters: [Parameter], closeBracketToken: Token, resultType: Type?, body: [Statement], closeBraceToken: Token) { + public init(funcToken: Token, attributes: [Attribute], modifiers: [Token], identifier: Identifier, parameters: [Parameter], closeBracketToken: Token, resultType: Type?, body: [Statement], closeBraceToken: Token, scopeContext: ScopeContext? = nil) { self.funcToken = funcToken self.attributes = attributes self.modifiers = modifiers @@ -226,6 +222,7 @@ public struct FunctionDeclaration: SourceEntity { self.resultType = resultType self.body = body self.closeBraceToken = closeBraceToken + self.scopeContext = scopeContext } private func hasModifier(kind: Token.Kind) -> Bool { @@ -250,6 +247,9 @@ public struct InitializerDeclaration: SourceEntity { public var sourceLocation: SourceLocation { return initToken.sourceLocation } + + // Contextual information for the scope defined by the function. + public var scopeContext: ScopeContext? = nil /// The non-implicit parameters of the initializer. public var explicitParameters: [Parameter] { @@ -259,19 +259,14 @@ public struct InitializerDeclaration: SourceEntity { /// A function declaration equivalent of the initializer. public var asFunctionDeclaration: FunctionDeclaration { let dummyIdentifier = Identifier(identifierToken: Token(kind: .identifier("init"), sourceLocation: initToken.sourceLocation)) - return FunctionDeclaration(funcToken: initToken, attributes: attributes, modifiers: modifiers, identifier: dummyIdentifier, parameters: parameters, closeBracketToken: closeBracketToken, resultType: nil, body: body, closeBraceToken: closeBracketToken) - } - - /// The parameters of the initializer, as variable declaration values. - public var parametersAsVariableDeclarations: [VariableDeclaration] { - return asFunctionDeclaration.parametersAsVariableDeclarations + return FunctionDeclaration(funcToken: initToken, attributes: attributes, modifiers: modifiers, identifier: dummyIdentifier, parameters: parameters, closeBracketToken: closeBracketToken, resultType: nil, body: body, closeBraceToken: closeBracketToken, scopeContext: scopeContext) } public var isPublic: Bool { return asFunctionDeclaration.isPublic } - public init(initToken: Token, attributes: [Attribute], modifiers: [Token], parameters: [Parameter], closeBracketToken: Token, body: [Statement], closeBraceToken: Token) { + public init(initToken: Token, attributes: [Attribute], modifiers: [Token], parameters: [Parameter], closeBracketToken: Token, body: [Statement], closeBraceToken: Token, scopeContext: ScopeContext? = nil) { self.initToken = initToken self.attributes = attributes self.modifiers = modifiers @@ -279,6 +274,7 @@ public struct InitializerDeclaration: SourceEntity { self.closeBracketToken = closeBracketToken self.body = body self.closeBraceToken = closeBraceToken + self.scopeContext = scopeContext } } @@ -333,6 +329,10 @@ public struct Parameter: SourceEntity { return .spanning(identifier, to: type) } + public var asVariableDeclaration: VariableDeclaration { + return VariableDeclaration(declarationToken: nil, identifier: identifier, type: type) + } + public init(identifier: Identifier, type: Type, implicitToken: Token?) { self.identifier = identifier self.type = type @@ -416,6 +416,10 @@ public struct Type: SourceEntity { } } + public var isUserDefinedType: Bool { + return !isBuiltInType + } + public var isEventType: Bool { return self == .basicType(.event) } @@ -562,6 +566,7 @@ public indirect enum Expression: SourceEntity { case variableDeclaration(VariableDeclaration) case bracketedExpression(Expression) case subscriptExpression(SubscriptExpression) + case sequence([Expression]) public var sourceLocation: SourceLocation { switch self { @@ -576,6 +581,7 @@ public indirect enum Expression: SourceEntity { case .variableDeclaration(let variableDeclaration): return variableDeclaration.sourceLocation case .bracketedExpression(let bracketedExpression): return bracketedExpression.sourceLocation case .subscriptExpression(let subscriptExpression): return subscriptExpression.sourceLocation + case .sequence(let expressions): return expressions.first!.sourceLocation } } @@ -601,11 +607,27 @@ public indirect enum Expression: SourceEntity { } public var enclosingType: String? { - guard case .identifier(let identifier) = self else { - return nil + switch self { + case .identifier(let identifier): return identifier.enclosingType ?? identifier.name + case .inoutExpression(let inoutExpression): return inoutExpression.expression.enclosingType + case .binaryExpression(let binaryExpression): return binaryExpression.lhs.enclosingType + case .bracketedExpression(let expression): return expression.enclosingType + case .variableDeclaration(let variableDeclaration): return variableDeclaration.identifier.name + case .subscriptExpression(let subscriptExpression): return subscriptExpression.baseIdentifier.enclosingType + default : return nil + } + } + + public var enclosingIdentifier: Identifier? { + switch self { + case .identifier(let identifier): return identifier + case .inoutExpression(let inoutExpression): return inoutExpression.expression.enclosingIdentifier + case .variableDeclaration(let variableDeclaration): return variableDeclaration.identifier + case .binaryExpression(let binaryExpression): return binaryExpression.lhs.enclosingIdentifier + case .bracketedExpression(let expression): return expression.enclosingIdentifier + case .subscriptExpression(let subscriptExpression): return subscriptExpression.baseIdentifier + default : return nil } - - return identifier.enclosingType } public var isLiteral: Bool { @@ -793,6 +815,12 @@ public struct IfStatement: SourceEntity { public var sourceLocation: SourceLocation { return .spanning(ifToken, to: condition) } + + // Contextual information for the scope defined by the if body. + public var ifBodyScopeContext: ScopeContext? = nil + + // Contextual information for the scope defined by the else body. + public var elseBodyScopeContext: ScopeContext? = nil public var endsWithReturnStatement: Bool { return body.contains { statement in diff --git a/Sources/AST/ASTDumper.swift b/Sources/AST/ASTDumper.swift index 83ae6395..b6d4ff72 100644 --- a/Sources/AST/ASTDumper.swift +++ b/Sources/AST/ASTDumper.swift @@ -272,6 +272,7 @@ public class ASTDumper { case .self(let token): self.dump(token) case .variableDeclaration(let variableDeclaration): self.dump(variableDeclaration) case .subscriptExpression(let subscriptExpression): self.dump(subscriptExpression) + case .sequence(let expressions): expressions.forEach { self.dump($0) } } } } diff --git a/Sources/AST/ASTPass.swift b/Sources/AST/ASTPass.swift index 75673f63..f132c2ea 100644 --- a/Sources/AST/ASTPass.swift +++ b/Sources/AST/ASTPass.swift @@ -273,7 +273,6 @@ public struct AnyASTPass: ASTPass { return base.postProcess(subscriptExpression: subscriptExpression, passContext: passContext) } - public func postProcess(returnStatement: ReturnStatement, passContext: ASTPassContext) -> ASTPassResult { return base.postProcess(returnStatement: returnStatement, passContext: passContext) } @@ -282,11 +281,3 @@ public struct AnyASTPass: ASTPass { return base.postProcess(ifStatement: ifStatement, passContext: passContext) } } - -extension ASTPass { - public func enclosingTypeIdentifier(in passContext: ASTPassContext) -> Identifier { - return passContext.contractBehaviorDeclarationContext?.contractIdentifier ?? - passContext.structDeclarationContext?.structIdentifier ?? - passContext.contractStateDeclarationContext!.contractIdentifier - } -} diff --git a/Sources/AST/ASTPassContext.swift b/Sources/AST/ASTPassContext.swift index fbad974d..9cb6db13 100644 --- a/Sources/AST/ASTPassContext.swift +++ b/Sources/AST/ASTPassContext.swift @@ -99,6 +99,18 @@ extension ASTPassContext { get { return self[IsFunctionCallContextEntry.self] } set { self[IsFunctionCallContextEntry.self] = newValue } } + + /// The identifier of the enclosing type (a contract or a struct). + public var enclosingTypeIdentifier: Identifier? { + return contractBehaviorDeclarationContext?.contractIdentifier ?? + structDeclarationContext?.structIdentifier ?? + contractStateDeclarationContext?.contractIdentifier + } + + /// Whether we are visiting a node in a function declaration or initializer. + public var inFunctionOrInitializer: Bool { + return functionDeclarationContext != nil || functionDeclarationContext != nil + } } /// A entry used to index in an `ASTPassContext`. diff --git a/Sources/AST/ASTVisitor.swift b/Sources/AST/ASTVisitor.swift index b10f9fdb..70470540 100644 --- a/Sources/AST/ASTVisitor.swift +++ b/Sources/AST/ASTVisitor.swift @@ -194,7 +194,7 @@ public struct ASTVisitor { processResult.passContext.functionDeclarationContext = functionDeclarationContext - processResult.passContext.scopeContext!.localVariables.append(contentsOf: functionDeclaration.parametersAsVariableDeclarations) + processResult.passContext.scopeContext!.parameters.append(contentsOf: functionDeclaration.parameters) processResult.element.body = processResult.element.body.map { statement in return processResult.combining(visit(statement, passContext: processResult.passContext)) @@ -221,7 +221,7 @@ public struct ASTVisitor { processResult.passContext.initializerDeclarationContext = initializerDeclarationContext let functionDeclaration = initializerDeclaration.asFunctionDeclaration - processResult.passContext.scopeContext!.localVariables.append(contentsOf: functionDeclaration.parametersAsVariableDeclarations) + processResult.passContext.scopeContext!.parameters.append(contentsOf: functionDeclaration.parameters) processResult.element.body = processResult.element.body.map { statement in return processResult.combining(visit(statement, passContext: processResult.passContext)) @@ -305,6 +305,10 @@ public struct ASTVisitor { processResult.element = .variableDeclaration(processResult.combining(visit(variableDeclaration, passContext: processResult.passContext))) case .subscriptExpression(let subscriptExpression): processResult.element = .subscriptExpression(processResult.combining(visit(subscriptExpression, passContext: processResult.passContext))) + case .sequence(let elements): + processResult.element = .sequence(elements.map { element in + return processResult.combining(visit(element, passContext: passContext)) + }) } let postProcessResult = pass.postProcess(expression: processResult.element, passContext: processResult.passContext) @@ -432,11 +436,21 @@ public struct ASTVisitor { processResult.element.body = processResult.element.body.map { statement in return processResult.combining(visit(statement, passContext: processResult.passContext)) } + + if processResult.element.ifBodyScopeContext == nil { + processResult.element.ifBodyScopeContext = processResult.passContext.scopeContext + } + processResult.passContext.scopeContext = scopeContext processResult.element.elseBody = processResult.element.elseBody.map { statement in return processResult.combining(visit(statement, passContext: processResult.passContext)) } + + if processResult.element.elseBodyScopeContext == nil { + processResult.element.elseBodyScopeContext = processResult.passContext.scopeContext + } + processResult.passContext.scopeContext = scopeContext let postProcessResult = pass.postProcess(ifStatement: processResult.element, passContext: processResult.passContext) diff --git a/Sources/AST/ASTVisitorContext.swift b/Sources/AST/ASTVisitorContext.swift index 4e4a183a..960371ae 100644 --- a/Sources/AST/ASTVisitorContext.swift +++ b/Sources/AST/ASTVisitorContext.swift @@ -56,22 +56,48 @@ public struct InitializerDeclarationContext { /// Contextual information used when visiting a scope, such as the local variables which are accessible in that /// scope. -public struct ScopeContext { +public struct ScopeContext: Equatable { + public var parameters = [Parameter]() public var localVariables = [VariableDeclaration]() - public init(localVariables: [VariableDeclaration] = []) { + public init(parameters: [Parameter] = [], localVariables: [VariableDeclaration] = []) { + self.parameters = parameters self.localVariables = localVariables } + public func containsParameterDeclaration(for name: String) -> Bool { + return parameters.contains { $0.identifier.name == name } + } + public func containsVariableDeclaration(for name: String) -> Bool { return localVariables.contains { $0.identifier.name == name } } - public func variableDeclaration(for name: String) -> VariableDeclaration? { - return localVariables.first(where: { $0.identifier.name == name }) + public func containsDeclaration(for name: String) -> Bool { + return containsParameterDeclaration(for: name) || containsVariableDeclaration(for: name) + } + + public func declaration(for name: String) -> VariableDeclaration? { + let all = localVariables + parameters.map { $0.asVariableDeclaration } + return all.first(where: { $0.identifier.name == name }) } public func type(for variable: String) -> Type.RawType? { - return localVariables.first(where: { $0.identifier.name == variable })?.type.rawType + let all = localVariables + parameters.map { $0.asVariableDeclaration } + return all.first(where: { $0.identifier.name == variable })?.type.rawType + } + + /// Returns the parameter name for the enclosing identifier of the given expression. + /// + /// For example, when given the expression "a.foo.x", the function will return "a" if "a" is a parameter to the + /// function. + public func enclosingParameter(expression: Expression, enclosingTypeName: String) -> String? { + guard expression.enclosingType != enclosingTypeName, + let enclosingIdentifier = expression.enclosingIdentifier, + containsParameterDeclaration(for: enclosingIdentifier.name) else { + return nil + } + + return enclosingIdentifier.name } } diff --git a/Sources/AST/Environment.swift b/Sources/AST/Environment.swift index 32277c7f..5271e5ab 100644 --- a/Sources/AST/Environment.swift +++ b/Sources/AST/Environment.swift @@ -91,12 +91,6 @@ public struct Environment { public func isInitializerCall(_ functionCall: FunctionCall) -> Bool { return isStructDeclared(functionCall.identifier.name) } - - /// Whether the given type is a reference type (a contract). - public func isReferenceType(_ type: RawTypeIdentifier) -> Bool { - // TODO: it should be possible to pass structs by value as well. - return isContractDeclared(type) || isStructDeclared(type) - } /// Whether a property is defined in a type. public func isPropertyDefined(_ property: String, enclosingType: RawTypeIdentifier) -> Bool { @@ -313,6 +307,7 @@ public struct Environment { return type(ofArrayLiteral: arrayLiteral, enclosingType: enclosingType, scopeContext: scopeContext) case .dictionaryLiteral(let dictionaryLiteral): return type(ofDictionaryLiteral: dictionaryLiteral, enclosingType: enclosingType, scopeContext: scopeContext) + case .sequence(_): fatalError() } } diff --git a/Sources/IRGen/Function/FunctionContext.swift b/Sources/IRGen/Function/FunctionContext.swift index 52312e64..fd3856bc 100644 --- a/Sources/IRGen/Function/FunctionContext.swift +++ b/Sources/IRGen/Function/FunctionContext.swift @@ -18,6 +18,6 @@ struct FunctionContext { /// The type in which the function is declared. var enclosingTypeName: String - /// Whether the function is declared in a contract. - var isInContractFunction: Bool + /// Whether the function is declared in a struct. + var isInStructFunction: Bool } diff --git a/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift b/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift index 2df7952f..81ce8c88 100644 --- a/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift +++ b/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift @@ -19,15 +19,19 @@ struct IULIACallerCapabilityChecks { let type = environment.type(of: callerCapability.identifier.name, enclosingType: functionContext.enclosingTypeName)! let offset = environment.propertyOffset(for: callerCapability.name, enclosingType: functionContext.enclosingTypeName)! + switch type { case .fixedSizeArrayType(_, let size): return (0.. String { @@ -61,10 +61,13 @@ struct IULIAContractInitializer { let body = IULIAFunctionBody(functionDeclaration: initializerDeclaration.asFunctionDeclaration, typeIdentifier: typeIdentifier, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, environment: environment, isContractFunction: isContractFunction).rendered() + // TODO: Remove IULIARuntimeFunctionDeclaration.store once constructor code and function code is unified. + return """ \(parameterBindings) \(defaultValuesAssignments) \(body) + \(IULIARuntimeFunctionDeclaration.store) """ } diff --git a/Sources/IRGen/Function/IULIAExpression.swift b/Sources/IRGen/Function/IULIAExpression.swift index bb2a18f5..5613a54f 100644 --- a/Sources/IRGen/Function/IULIAExpression.swift +++ b/Sources/IRGen/Function/IULIAExpression.swift @@ -30,7 +30,7 @@ struct IULIAExpression { case .identifier(let identifier): return IULIAIdentifier(identifier: identifier, asLValue: asLValue).rendered(functionContext: functionContext) case .variableDeclaration(let variableDeclaration): - return IULIAVariableDeclaration(variableDeclaration: variableDeclaration).rendered() + return IULIAVariableDeclaration(variableDeclaration: variableDeclaration).rendered(functionContext: functionContext) case .literal(let literal): return IULIALiteralToken(literalToken: literal).rendered() case .arrayLiteral(let arrayLiteral): @@ -43,6 +43,8 @@ struct IULIAExpression { return IULIASelf(selfToken: self, asLValue: asLValue).rendered() case .subscriptExpression(let subscriptExpression): return IULIASubscriptExpression(subscriptExpression: subscriptExpression, asLValue: asLValue).rendered(functionContext: functionContext) + case .sequence(let expressions): + return expressions.map { IULIAExpression(expression: $0, asLValue: asLValue).rendered(functionContext: functionContext) }.joined(separator: "\n") } } } @@ -94,38 +96,55 @@ struct IULIAPropertyAccess { var asLValue: Bool func rendered(functionContext: FunctionContext) -> String { - let lhsOffset: String - let environment = functionContext.environment let scopeContext = functionContext.scopeContext let enclosingTypeName = functionContext.enclosingTypeName - let isInContractFunction = functionContext.isInContractFunction - - if case .identifier(let lhsIdentifier) = lhs { - if let enclosingType = lhs.enclosingType, let offset = environment.propertyOffset(for: lhsIdentifier.name, enclosingType: enclosingType) { - lhsOffset = "\(offset)" - } else { - lhsOffset = "\(environment.propertyOffset(for: lhsIdentifier.name, enclosingType: enclosingTypeName)!)" - } - } else { - lhsOffset = IULIAExpression(expression: lhs, asLValue: true).rendered(functionContext: functionContext) - } + let isInStructFunction = functionContext.isInStructFunction + + var isMemoryAccess: Bool = false let lhsType = environment.type(of: lhs, enclosingType: enclosingTypeName, scopeContext: scopeContext) let rhsOffset = IULIAPropertyOffset(expression: rhs, enclosingType: lhsType).rendered(functionContext: functionContext) let offset: String - if !isInContractFunction { + if isInStructFunction { + let enclosingName: String + if let enclosingParameter = functionContext.scopeContext.enclosingParameter(expression: lhs, enclosingTypeName: functionContext.enclosingTypeName) { + enclosingName = enclosingParameter + } else { + enclosingName = "flintSelf" + } + // For struct parameters, access the property by an offset to _flintSelf (the receiver's address). - offset = "add(_flintSelf, \(rhsOffset))" + offset = IULIARuntimeFunction.addOffset(base: enclosingName.mangled, offset: rhsOffset, inMemory: Mangler.isMem(for: enclosingName).mangled) } else { - offset = "add(\(lhsOffset), \(rhsOffset))" + let lhsOffset: String + if case .identifier(let lhsIdentifier) = lhs { + if let enclosingType = lhsIdentifier.enclosingType, let offset = environment.propertyOffset(for: lhsIdentifier.name, enclosingType: enclosingType) { + lhsOffset = "\(offset)" + } else if functionContext.scopeContext.containsVariableDeclaration(for: lhsIdentifier.name) { + lhsOffset = lhsIdentifier.name.mangled + isMemoryAccess = true + } else { + lhsOffset = "\(environment.propertyOffset(for: lhsIdentifier.name, enclosingType: enclosingTypeName)!)" + } + } else { + lhsOffset = IULIAExpression(expression: lhs, asLValue: true).rendered(functionContext: functionContext) + } + + offset = IULIARuntimeFunction.addOffset(base: lhsOffset, offset: rhsOffset, inMemory: isMemoryAccess) } if asLValue { return offset } - return "sload(\(offset))" + + if isInStructFunction, !isMemoryAccess { + let lhsEnclosingIdentifier = lhs.enclosingIdentifier?.name.mangled ?? "flintSelf".mangled + return IULIARuntimeFunction.load(address: offset, inMemory: Mangler.isMem(for: lhsEnclosingIdentifier)) + } + + return IULIARuntimeFunction.load(address: offset, inMemory: isMemoryAccess) } } @@ -157,11 +176,30 @@ struct IULIAAssignment { case .variableDeclaration(let variableDeclaration): return "let \(Mangler.mangleName(variableDeclaration.identifier.name)) := \(rhsCode)" case .identifier(let identifier) where identifier.enclosingType == nil: - return "\(Mangler.mangleName(identifier.name)) := \(rhsCode)" + return "\(identifier.name.mangled) := \(rhsCode)" default: - // LHS refers to a storage property. + // LHS refers to a property in storage or memory. + let lhsCode = IULIAExpression(expression: lhs, asLValue: true).rendered(functionContext: functionContext) - return "sstore(\(lhsCode), \(rhsCode))" + + if functionContext.isInStructFunction { + let enclosingName: String + if let enclosingParameter = functionContext.scopeContext.enclosingParameter(expression: lhs, enclosingTypeName: functionContext.enclosingTypeName) { + enclosingName = enclosingParameter + } else { + enclosingName = "flintSelf" + } + return IULIARuntimeFunction.store(address: lhsCode, value: rhsCode, inMemory: Mangler.isMem(for: enclosingName).mangled) + } + + let isMemoryAccess: Bool + if let enclosingIdentifier = lhs.enclosingIdentifier, functionContext.scopeContext.containsVariableDeclaration(for: enclosingIdentifier.name) { + isMemoryAccess = true + } else { + isMemoryAccess = false + } + + return IULIARuntimeFunction.store(address: lhsCode, value: rhsCode, inMemory: isMemoryAccess) } } } @@ -178,11 +216,23 @@ struct IULIAFunctionCall { } let args: String = functionCall.arguments.map({ argument in - let type = environment.type(of: argument, enclosingType: functionContext.enclosingTypeName, scopeContext: functionContext.scopeContext) - return IULIAExpression(expression: argument, asLValue: functionContext.environment.isReferenceType(type.name)).rendered(functionContext: functionContext) + var type: Type.RawType + var argument = argument + + // If the receiver is a local variable of user-defined type, extract it. + if case .inoutExpression(let inoutExpression) = argument, + case .variableDeclaration(let variableDeclaration) = inoutExpression.expression { + type = variableDeclaration.type.rawType + argument = .identifier(variableDeclaration.identifier) + } else { + type = environment.type(of: argument, enclosingType: functionContext.enclosingTypeName, scopeContext: functionContext.scopeContext) + } + + return IULIAExpression(expression: argument, asLValue: type.isUserDefinedType).rendered(functionContext: functionContext) }).joined(separator: ", ") return "\(functionCall.identifier.name)(\(args))" } + } /// Generates code for an event call. @@ -230,7 +280,7 @@ struct IULIAIdentifier { if let _ = identifier.enclosingType { return IULIAPropertyAccess(lhs: .self(Token(kind: .self, sourceLocation: identifier.sourceLocation)), rhs: .identifier(identifier), asLValue: asLValue).rendered(functionContext: functionContext) } - return Mangler.mangleName(identifier.name) + return identifier.name.mangled } static func mangleName(_ name: String) -> String { @@ -242,8 +292,9 @@ struct IULIAIdentifier { struct IULIAVariableDeclaration { var variableDeclaration: VariableDeclaration - func rendered() -> String { - return "var \(variableDeclaration.identifier)" + func rendered(functionContext: FunctionContext) -> String { + let allocate = IULIARuntimeFunction.allocateMemory(size: functionContext.environment.size(of: variableDeclaration.type.rawType) * EVM.wordSize) + return "let \(variableDeclaration.identifier.name) := \(allocate)" } } @@ -299,7 +350,7 @@ struct IULIASubscriptExpression { switch type { case .arrayType(let elementType): - let storageArrayOffset = "\(IULIARuntimeFunction.storageArrayOffset.rawValue)(\(offset), \(indexExpressionCode))" + let storageArrayOffset = IULIARuntimeFunction.storageArrayOffset(arrayOffset: offset, index: indexExpressionCode) if asLValue { return storageArrayOffset } else { @@ -310,7 +361,7 @@ struct IULIASubscriptExpression { } case .fixedSizeArrayType(let elementType, _): let typeSize = environment.size(of: type) - let storageArrayOffset = "\(IULIARuntimeFunction.storageFixedSizeArrayOffset.rawValue)(\(offset), \(indexExpressionCode), \(typeSize))" + let storageArrayOffset = IULIARuntimeFunction.storageFixedSizeArrayOffset(arrayOffset: offset, index: indexExpressionCode, arraySize: typeSize) if asLValue { return storageArrayOffset } else { @@ -324,7 +375,7 @@ struct IULIASubscriptExpression { fatalError("Dictionary keys of size > 1 are not supported yet.") } - let storageDictionaryOffsetForKey = "\(IULIARuntimeFunction.storageDictionaryOffsetForKey.rawValue)(\(offset), \(indexExpressionCode))" + let storageDictionaryOffsetForKey = IULIARuntimeFunction.storageDictionaryOffsetForKey(dictionaryOffset: offset, key: indexExpressionCode) if asLValue { return "\(storageDictionaryOffsetForKey)" diff --git a/Sources/IRGen/Function/IULIAFunction.swift b/Sources/IRGen/Function/IULIAFunction.swift index 2ea6751c..064a17b5 100644 --- a/Sources/IRGen/Function/IULIAFunction.swift +++ b/Sources/IRGen/Function/IULIAFunction.swift @@ -23,7 +23,7 @@ struct IULIAFunction { var isContractFunction = false var functionContext: FunctionContext { - return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInContractFunction: isContractFunction) + return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInStructFunction: !isContractFunction) } init(functionDeclaration: FunctionDeclaration, typeIdentifier: Identifier, capabilityBinding: Identifier? = nil, callerCapabilities: [CallerCapability] = [], environment: Environment) { @@ -50,11 +50,7 @@ struct IULIAFunction { /// The function's parameters and caller capability binding, as variable declarations in a `ScopeContext`. var scopeContext: ScopeContext { - var localVariables = functionDeclaration.parametersAsVariableDeclarations - if let capabilityBinding = capabilityBinding { - localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .basicType(.address), identifier: capabilityBinding))) - } - return ScopeContext(localVariables: localVariables) + return functionDeclaration.scopeContext! } var parameterCanonicalTypes: [CanonicalType] { @@ -101,15 +97,11 @@ struct IULIAFunctionBody { /// The function's parameters and caller capability binding, as variable declarations in a `ScopeContext`. var scopeContext: ScopeContext { - var localVariables = functionDeclaration.parametersAsVariableDeclarations - if let capabilityBinding = capabilityBinding { - localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .basicType(.address), identifier: capabilityBinding))) - } - return ScopeContext(localVariables: localVariables) + return functionDeclaration.scopeContext! } var functionContext: FunctionContext { - return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInContractFunction: isContractFunction) + return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInStructFunction: !isContractFunction) } func rendered() -> String { @@ -120,7 +112,7 @@ struct IULIAFunctionBody { // Assign a caller capaiblity binding to a local variable. let capabilityBindingDeclaration: String if let capabilityBinding = capabilityBinding { - capabilityBindingDeclaration = "let \(Mangler.mangleName(capabilityBinding.name)) := caller()\n" + capabilityBindingDeclaration = "let \(capabilityBinding.name.mangled) := caller()\n" } else { capabilityBindingDeclaration = "" } @@ -128,7 +120,7 @@ struct IULIAFunctionBody { // Assign Wei value sent to a @payable function to a local variable. let payableValueDeclaration: String if let payableValueParameter = functionDeclaration.firstPayableValueParameter { - payableValueDeclaration = "let \(Mangler.mangleName(payableValueParameter.identifier.name)) := callvalue()\n" + payableValueDeclaration = "let \(payableValueParameter.identifier.name.mangled) := callvalue()\n" } else { payableValueDeclaration = "" } diff --git a/Sources/IRGen/Function/IULIAStatement.swift b/Sources/IRGen/Function/IULIAStatement.swift index bc6e6deb..b71190ce 100644 --- a/Sources/IRGen/Function/IULIAStatement.swift +++ b/Sources/IRGen/Function/IULIAStatement.swift @@ -26,6 +26,10 @@ struct IULIAIfStatement { func rendered(functionContext: FunctionContext) -> String { let condition = IULIAExpression(expression: ifStatement.condition).rendered(functionContext: functionContext) + + var functionContext = functionContext + functionContext.scopeContext = ifStatement.ifBodyScopeContext! + let body = ifStatement.body.map { statement in return IULIAStatement(statement: statement).rendered(functionContext: functionContext) }.joined(separator: "\n") @@ -34,13 +38,14 @@ struct IULIAIfStatement { ifCode = """ switch \(condition) case 1 { - \(body.indented(by: 2)) + \(body.indented(by: 2)) } """ var elseCode = "" if !ifStatement.elseBody.isEmpty { + functionContext.scopeContext = ifStatement.elseBodyScopeContext! let body = ifStatement.elseBody.map { statement in if case .returnStatement(_) = statement { fatalError("Return statements in else blocks are not supported yet") @@ -49,7 +54,7 @@ struct IULIAIfStatement { }.joined(separator: "\n") elseCode = """ default { - \(body.indented(by: 2)) + \(body.indented(by: 2)) } """ } diff --git a/Sources/IRGen/IULIAContract.swift b/Sources/IRGen/IULIAContract.swift index 998b443f..32f1cafd 100644 --- a/Sources/IRGen/IULIAContract.swift +++ b/Sources/IRGen/IULIAContract.swift @@ -46,7 +46,7 @@ struct IULIAContract { let initializerBody = renderPublicInitializer() // Generate runtime functions. - let runtimeFunctionsDeclarations = IULIARuntimeFunction.all.map { $0.declaration }.joined(separator: "\n\n").indented(by: 6) + let runtimeFunctionsDeclarations = IULIARuntimeFunction.allDeclarations.joined(separator: "\n\n").indented(by: 6) // Main contract body. return """ @@ -58,6 +58,9 @@ struct IULIAContract { function () public payable { assembly { + // Memory address 0x40 holds the next available memory location. + mstore(0x40, 0x60) + \(selectorCode) // User-defined functions @@ -98,7 +101,7 @@ struct IULIAContract { let initializer = IULIAContractInitializer(initializerDeclaration: initializerDeclaration, typeIdentifier: contractDeclaration.identifier, propertiesInEnclosingType: contractDeclaration.variableDeclarations, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, environment: environment, isContractFunction: true).rendered() let parameters = initializerDeclaration.parameters.map { parameter in - let parameterName = Mangler.mangleName(parameter.identifier.name) + let parameterName = parameter.identifier.name.mangled return "\(CanonicalType(from: parameter.type.rawType)!.rawValue) \(parameterName)" }.joined(separator: ", ") @@ -114,7 +117,7 @@ struct IULIAContract { func synthesizeInitializer() -> InitializerDeclaration { let sourceLocation = contractDeclaration.sourceLocation - return InitializerDeclaration(initToken: Token(kind: .init, sourceLocation: sourceLocation), attributes: [], modifiers: [Token(kind: .public, sourceLocation: sourceLocation)], parameters: [], closeBracketToken: Token(kind: .punctuation(.closeBracket), sourceLocation: sourceLocation), body: [], closeBraceToken: Token(kind: .punctuation(.closeBrace), sourceLocation: sourceLocation)) + return InitializerDeclaration(initToken: Token(kind: .init, sourceLocation: sourceLocation), attributes: [], modifiers: [Token(kind: .public, sourceLocation: sourceLocation)], parameters: [], closeBracketToken: Token(kind: .punctuation(.closeBracket), sourceLocation: sourceLocation), body: [], closeBraceToken: Token(kind: .punctuation(.closeBrace), sourceLocation: sourceLocation), scopeContext: ScopeContext(localVariables: [])) } /// Finds the contract's public initializer, if any is declared, and returns the enclosing contract behavior declaration. diff --git a/Sources/IRGen/IULIAInterface.swift b/Sources/IRGen/IULIAInterface.swift index 6ffcacd0..a6fd5d30 100644 --- a/Sources/IRGen/IULIAInterface.swift +++ b/Sources/IRGen/IULIAInterface.swift @@ -68,7 +68,7 @@ struct IULIAInterface { } func render(_ functionParameter: Parameter) -> String { - return "\(CanonicalType(from: functionParameter.type.rawType)!.rawValue) \(Mangler.mangleName(functionParameter.identifier.name))" + return "\(CanonicalType(from: functionParameter.type.rawType)!.rawValue) \(functionParameter.identifier.name.mangled)" } } diff --git a/Sources/IRGen/IULIAPreprocessor.swift b/Sources/IRGen/IULIAPreprocessor.swift index e33bc168..73155027 100644 --- a/Sources/IRGen/IULIAPreprocessor.swift +++ b/Sources/IRGen/IULIAPreprocessor.swift @@ -42,7 +42,7 @@ public struct IULIAPreprocessor: ASTPass { var structMember = structMember if case .initializerDeclaration(var initializerDeclaration) = structMember { - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name let propertiesInEnclosingType = passContext.environment!.propertyDeclarations(in: enclosingType) let defaultValueAssignments = propertiesInEnclosingType.compactMap { declaration -> Statement? in @@ -64,22 +64,46 @@ public struct IULIAPreprocessor: ASTPass { } public func process(variableDeclaration: VariableDeclaration, passContext: ASTPassContext) -> ASTPassResult { + var passContext = passContext + + if let _ = passContext.functionDeclarationContext { + // We're in a function. Record the local variable declaration. + passContext.scopeContext?.localVariables += [variableDeclaration] + } + return ASTPassResult(element: variableDeclaration, diagnostics: [], passContext: passContext) } public func process(functionDeclaration: FunctionDeclaration, passContext: ASTPassContext) -> ASTPassResult { var functionDeclaration = functionDeclaration - // For struct functions, add `flintSelf` to the beginning of the parameters list. if let structDeclarationContext = passContext.structDeclarationContext { - let selfIdentifier = Identifier(identifierToken: Token(kind: .identifier("flintSelf"), sourceLocation: SourceLocation(line: 0, column: 0, length: 0))) - functionDeclaration.parameters.insert(Parameter(identifier: selfIdentifier, type: Type(inferredType: .userDefinedType(structDeclarationContext.structIdentifier.name), identifier: selfIdentifier), implicitToken: nil), at: 0) - let name = Mangler.mangleName(functionDeclaration.identifier.name, enclosingType: structDeclarationContext.structIdentifier.name) + // For struct functions, add `flintSelf` to the beginning of the parameters list. + let parameter = constructParameter(name: "flintSelf", type: .inoutType(.userDefinedType(structDeclarationContext.structIdentifier.name)), sourceLocation: functionDeclaration.sourceLocation) + functionDeclaration.parameters.insert(parameter, at: 0) + + let name = Mangler.mangleName(functionDeclaration.identifier.name, enclosingType: passContext.enclosingTypeIdentifier!.name) functionDeclaration.identifier = Identifier(identifierToken: Token(kind: .identifier(name), sourceLocation: functionDeclaration.identifier.sourceLocation)) } + + // Add an isMem parameter for each struct parameter. + let dynamicParameters = functionDeclaration.parameters.enumerated().filter { $0.1.type.rawType.isDynamicType } + + var offset = 0 + for (index, parameter) in dynamicParameters { + let isMemParameter = constructParameter(name: Mangler.isMem(for: parameter.identifier.name), type: .basicType(.bool), sourceLocation: parameter.sourceLocation) + functionDeclaration.parameters.insert(isMemParameter, at: index + 1 + offset) + offset += 1 + } + return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + func constructParameter(name: String, type: Type.RawType, sourceLocation: SourceLocation) -> Parameter { + let identifier = Identifier(identifierToken: Token(kind: .identifier(name), sourceLocation: sourceLocation)) + return Parameter(identifier: identifier, type: Type(inferredType: type, identifier: identifier), implicitToken: nil) + } + public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) } @@ -117,9 +141,18 @@ public struct IULIAPreprocessor: ASTPass { case .functionCall(var functionCall) = binaryExpression.rhs { if environment.isInitializerCall(functionCall) { // If we're initializing a struct, pass the lhs expression as the first parameter of the initializer call. - let inoutExpression = InoutExpression(ampersandToken: Token(kind: .punctuation(.ampersand), sourceLocation: binaryExpression.lhs.sourceLocation), expression: binaryExpression.lhs) + let ampersandToken: Token = Token(kind: .punctuation(.ampersand), sourceLocation: binaryExpression.lhs.sourceLocation) + let inoutExpression = InoutExpression(ampersandToken: ampersandToken, expression: binaryExpression.lhs) functionCall.arguments.insert(.inoutExpression(inoutExpression), at: 0) + expression = .functionCall(functionCall) + + if case .variableDeclaration(var variableDeclaration) = binaryExpression.lhs, + variableDeclaration.type.rawType.isUserDefinedType { + let mangled = variableDeclaration.identifier.name.mangled + variableDeclaration.identifier = Identifier(identifierToken: Token(kind: .identifier(mangled), sourceLocation: variableDeclaration.identifier.sourceLocation)) + expression = .sequence([.variableDeclaration(variableDeclaration), .functionCall(functionCall)]) + } } } @@ -181,13 +214,13 @@ public struct IULIAPreprocessor: ASTPass { var functionCall = functionCall let environment = passContext.environment! let receiverTrail = passContext.functionCallReceiverTrail ?? [] - + if environment.isStructDeclared(functionCall.identifier.name) { // We're calling an initializer. let mangledName = Mangler.mangleInitializer(enclosingType: functionCall.identifier.name) functionCall.identifier = Identifier(identifierToken: Token(kind: .identifier(mangledName), sourceLocation: functionCall.sourceLocation)) } else if !receiverTrail.isEmpty { - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name let scopeContext = passContext.scopeContext! let callerCapabilities = passContext.contractBehaviorDeclarationContext?.callerCapabilities ?? [] @@ -309,6 +342,29 @@ public struct IULIAPreprocessor: ASTPass { } public func postProcess(functionCall: FunctionCall, passContext: ASTPassContext) -> ASTPassResult { + var functionCall = functionCall + let enclosingType = passContext.enclosingTypeIdentifier!.name + + var callerCapabilities = [CallerCapability]() + let scopeContext = passContext.scopeContext! + + if let contractBehaviorDeclarationContext = passContext.contractBehaviorDeclarationContext { + callerCapabilities = contractBehaviorDeclarationContext.callerCapabilities + } + + var offset = 0 + for (index, argument) in functionCall.arguments.enumerated() { + let type = passContext.environment!.type(of: argument, enclosingType: enclosingType, callerCapabilities: callerCapabilities, scopeContext: scopeContext) + if type.isDynamicType { + var isMem = false + if let enclosingIdentifier = argument.enclosingIdentifier, scopeContext.containsDeclaration(for: enclosingIdentifier.name) { + isMem = true + } + + functionCall.arguments.insert(.literal(Token(kind: .literal(.boolean(isMem ? .true : .false)), sourceLocation: argument.sourceLocation)), at: index + offset + 1) + offset += 1 + } + } return ASTPassResult(element: functionCall, diagnostics: [], passContext: passContext) } diff --git a/Sources/IRGen/Mangler.swift b/Sources/IRGen/Mangler.swift index 16995aa9..9df5bb30 100644 --- a/Sources/IRGen/Mangler.swift +++ b/Sources/IRGen/Mangler.swift @@ -13,4 +13,15 @@ struct Mangler { static func mangleInitializer(enclosingType: String) -> String { return "\(enclosingType)_init" } + + /// Constructs the parameter name to indicate whether the given parameter is a memory reference. + static func isMem(for parameter: String) -> String { + return "\(parameter)$isMem" + } +} + +extension String { + var mangled: String { + return Mangler.mangleName(self) + } } diff --git a/Sources/IRGen/Runtime/IULIAFunctionSelector.swift b/Sources/IRGen/Runtime/IULIAFunctionSelector.swift index 9eb4e1f0..e7e980e7 100644 --- a/Sources/IRGen/Runtime/IULIAFunctionSelector.swift +++ b/Sources/IRGen/Runtime/IULIAFunctionSelector.swift @@ -41,8 +41,8 @@ struct IULIAFunctionSelector { let arguments = function.parameterCanonicalTypes.enumerated().map { arg -> String in let (index, type) = arg switch type { - case .address: return "\(IULIARuntimeFunction.decodeAsAddress.rawValue)(\(index))" - case .uint256, .bytes32: return "\(IULIARuntimeFunction.decodeAsUInt.rawValue)(\(index))" + case .address: return IULIARuntimeFunction.decodeAsAddress(offset: index) + case .uint256, .bytes32: return IULIARuntimeFunction.decodeAsUInt(offset: index) } } @@ -50,7 +50,7 @@ struct IULIAFunctionSelector { if let resultType = function.resultCanonicalType { switch resultType { - case .address, .uint256, .bytes32: return "\(IULIARuntimeFunction.return32Bytes.rawValue)(\(call))" + case .address, .uint256, .bytes32: return IULIARuntimeFunction.return32Bytes(value: call) } } diff --git a/Sources/IRGen/Runtime/IULIARuntimeFunction.swift b/Sources/IRGen/Runtime/IULIARuntimeFunction.swift index 469c1773..9a89281e 100644 --- a/Sources/IRGen/Runtime/IULIARuntimeFunction.swift +++ b/Sources/IRGen/Runtime/IULIARuntimeFunction.swift @@ -8,37 +8,97 @@ import Foundation /// The runtime functions used by Flint. -enum IULIARuntimeFunction: String { - case selector - case decodeAsAddress - case decodeAsUInt - case isValidCallerCapability - case isCallerCapabilityInArray - case return32Bytes - case isInvalidSubscriptExpression - case storageArrayOffset - case storageFixedSizeArrayOffset - case storageDictionaryOffsetForKey - - var declaration: String { - switch self { - case .selector: return IRRuntimeFunctionDeclaration.selector - case .decodeAsAddress: return IRRuntimeFunctionDeclaration.decodeAsAddress - case .decodeAsUInt: return IRRuntimeFunctionDeclaration.decodeAsUInt - case .isValidCallerCapability: return IRRuntimeFunctionDeclaration.isValidCallerCapability - case .return32Bytes: return IRRuntimeFunctionDeclaration.return32Bytes - case .isInvalidSubscriptExpression: return IRRuntimeFunctionDeclaration.isInvalidSubscriptExpression - case .storageArrayOffset: return IRRuntimeFunctionDeclaration.storageArrayOffset - case .isCallerCapabilityInArray: return IRRuntimeFunctionDeclaration.isCallerCapabilityInArray - case .storageFixedSizeArrayOffset: return IRRuntimeFunctionDeclaration.storageFixedSizeArrayOffset - case .storageDictionaryOffsetForKey: return IRRuntimeFunctionDeclaration.storageDictionaryOffsetForKey - } +enum IULIARuntimeFunction { + enum Identifiers: String { + case selector + case decodeAsAddress + case decodeAsUInt + case store + case load + case computeOffset + case allocateMemory + case isValidCallerCapability + case isCallerCapabilityInArray + case return32Bytes + case isInvalidSubscriptExpression + case storageArrayOffset + case storageFixedSizeArrayOffset + case storageDictionaryOffsetForKey + } + + static func selector() -> String { + return "\(Identifiers.selector)()" + } + + static func decodeAsAddress(offset: Int) -> String { + return "\(Identifiers.decodeAsAddress)(\(offset))" + } + + static func decodeAsUInt(offset: Int) -> String { + return "\(Identifiers.decodeAsUInt)(\(offset))" + } + + static func store(address: String, value: String, inMemory: Bool) -> String { + return "\(inMemory ? "mstore" : "sstore")(\(address), \(value))" + } + + static func store(address: String, value: String, inMemory: String) -> String { + return "\(Identifiers.store)(\(address), \(value), \(inMemory))" + } + + static func addOffset(base: String, offset: String, inMemory: Bool) -> String { + return inMemory ? "add(\(base), mul(\(EVM.wordSize), \(offset)))" : "add(\(base), \(offset))" + } + + static func addOffset(base: String, offset: String, inMemory: String) -> String { + return "\(Identifiers.computeOffset)(\(base), \(offset), \(inMemory))" + } + + static func load(address: String, inMemory: Bool) -> String { + return "\(inMemory ? "mload" : "sload")(\(address))" + } + + static func load(address: String, inMemory: String) -> String { + return "\(Identifiers.load)(\(address), \(inMemory))" + } + + static func allocateMemory(size: Int) -> String { + return "\(Identifiers.allocateMemory)(\(size))" + } + + static func isValidCallerCapability(address: String) -> String { + return "\(Identifiers.isValidCallerCapability)(\(address))" + } + + static func isCallerCapabilityInArray(arrayOffset: Int) -> String { + return "\(Identifiers.isCallerCapabilityInArray)(\(arrayOffset))" + } + + static func return32Bytes(value: String) -> String { + return "\(Identifiers.return32Bytes)(\(value))" + } + + static func isInvalidSubscriptExpression(index: Int, arraySize: Int) -> String { + return "\(Identifiers.isInvalidSubscriptExpression)(\(index), \(arraySize))" + } + + static func storageFixedSizeArrayOffset(arrayOffset: Int, index: String, arraySize: Int) -> String { + return "\(Identifiers.storageFixedSizeArrayOffset)(\(arrayOffset), \(index), \(arraySize))" + } + + static func storageArrayOffset(arrayOffset: Int, index: String) -> String { + return "\(Identifiers.storageArrayOffset)(\(arrayOffset), \(index))" + } + + static func storageDictionaryOffsetForKey(dictionaryOffset: Int, key: String) -> String { + return "\(Identifiers.storageDictionaryOffsetForKey)(\(dictionaryOffset), \(key))" } - static let all: [IULIARuntimeFunction] = [.selector, .decodeAsAddress, .decodeAsUInt, .isValidCallerCapability, .isCallerCapabilityInArray, .return32Bytes, .isInvalidSubscriptExpression, .storageArrayOffset, .storageFixedSizeArrayOffset, .storageDictionaryOffsetForKey] + static let allDeclarations: [String] = [IULIARuntimeFunctionDeclaration.selector, IULIARuntimeFunctionDeclaration.decodeAsAddress, IULIARuntimeFunctionDeclaration.decodeAsUInt, IULIARuntimeFunctionDeclaration.store, IULIARuntimeFunctionDeclaration.load, IULIARuntimeFunctionDeclaration.computeOffset, IULIARuntimeFunctionDeclaration.allocateMemory, IULIARuntimeFunctionDeclaration.isValidCallerCapability, IULIARuntimeFunctionDeclaration.isCallerCapabilityInArray, IULIARuntimeFunctionDeclaration.return32Bytes, IULIARuntimeFunctionDeclaration.isInvalidSubscriptExpression, IULIARuntimeFunctionDeclaration.storageArrayOffset, IULIARuntimeFunctionDeclaration.storageFixedSizeArrayOffset, IULIARuntimeFunctionDeclaration.storageDictionaryOffsetForKey] + } -fileprivate struct IRRuntimeFunctionDeclaration { +struct IULIARuntimeFunctionDeclaration { static let selector = """ function selector() -> ret { @@ -60,6 +120,53 @@ fileprivate struct IRRuntimeFunctionDeclaration { } """ + static let store = + """ + function store(ptr, val, mem) { + switch iszero(mem) + case 0 { + mstore(ptr, val) + } + default { + sstore(ptr, val) + } + } + """ + + static let load = + """ + function load(ptr, mem) -> ret { + switch iszero(mem) + case 0 { + ret := mload(ptr) + } + default { + ret := sload(ptr) + } + } + """ + + static let computeOffset = + """ + function computeOffset(base, offset, mem) -> ret { + switch iszero(mem) + case 0 { + ret := add(base, mul(offset, \(EVM.wordSize))) + } + default { + ret := add(base, offset) + } + } + """ + + static let allocateMemory = + """ + function allocateMemory(size) -> ret { + ret := mload(0x40) + mstore(0x40, add(ret, size)) + } + """ + static let isValidCallerCapability = """ function isValidCallerCapability(_address) -> ret { diff --git a/Sources/SemanticAnalyzer/SemanticAnalyzer.swift b/Sources/SemanticAnalyzer/SemanticAnalyzer.swift index c8512add..42482ce2 100644 --- a/Sources/SemanticAnalyzer/SemanticAnalyzer.swift +++ b/Sources/SemanticAnalyzer/SemanticAnalyzer.swift @@ -67,10 +67,8 @@ public struct SemanticAnalyzer: ASTPass { var diagnostics = [Diagnostic]() let environment = passContext.environment! - let inFunctionOrInitializer = passContext.functionDeclarationContext != nil || passContext.initializerDeclarationContext != nil - - if inFunctionOrInitializer { - if let conflict = passContext.scopeContext!.variableDeclaration(for: variableDeclaration.identifier.name) { + if passContext.inFunctionOrInitializer { + if let conflict = passContext.scopeContext!.declaration(for: variableDeclaration.identifier.name) { diagnostics.append(.invalidRedeclaration(variableDeclaration.identifier, originalSource: conflict.identifier)) } @@ -79,7 +77,7 @@ public struct SemanticAnalyzer: ASTPass { } else { // It's a property declaration. - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name if let conflict = environment.conflictingPropertyDeclaration(for: variableDeclaration.identifier, in: enclosingType) { diagnostics.append(.invalidRedeclaration(variableDeclaration.identifier, originalSource: conflict)) } @@ -116,7 +114,7 @@ public struct SemanticAnalyzer: ASTPass { public func process(functionDeclaration: FunctionDeclaration, passContext: ASTPassContext) -> ASTPassResult { var diagnostics = [Diagnostic]() - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name if let conflict = passContext.environment!.conflictingFunctionDeclaration(for: functionDeclaration.identifier, in: enclosingType) { diagnostics.append(.invalidRedeclaration(functionDeclaration.identifier, originalSource: conflict)) } @@ -158,7 +156,7 @@ public struct SemanticAnalyzer: ASTPass { } public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name let environment = passContext.environment! // Gather properties of the enclosing type which haven't been assigned a default value. @@ -178,10 +176,10 @@ public struct SemanticAnalyzer: ASTPass { } public func process(parameter: Parameter, passContext: ASTPassContext) -> ASTPassResult { - let type = parameter.type +// let type = parameter.type var diagnostics = [Diagnostic]() - if passContext.environment!.isReferenceType(type.name), !parameter.isInout { + if parameter.type.rawType.isUserDefinedType, !parameter.isInout { // Ensure all structs are passed by reference, for now. diagnostics.append(Diagnostic(severity: .error, sourceLocation: parameter.sourceLocation, message: "Structs cannot be passed by value yet, and have to be passed inout")) } @@ -215,7 +213,7 @@ public struct SemanticAnalyzer: ASTPass { // The identifier has no explicit enclosing type, such as in the expression `foo` instead of `a.foo`. let scopeContext = passContext.scopeContext! - if let variableDeclaration = scopeContext.variableDeclaration(for: identifier.name) { + if let variableDeclaration = scopeContext.declaration(for: identifier.name) { if variableDeclaration.isConstant, asLValue { // The variable is a constant but is attempted to be reassigned. diagnostics.append(.reassignmentToConstant(identifier, variableDeclaration.sourceLocation)) @@ -223,7 +221,7 @@ public struct SemanticAnalyzer: ASTPass { } else { // If the variable is not declared locally, assign its enclosing type to the struct or contract behavior // declaration in which the function appears. - identifier.enclosingType = enclosingTypeIdentifier(in: passContext).name + identifier.enclosingType = passContext.enclosingTypeIdentifier!.name } } @@ -306,7 +304,7 @@ public struct SemanticAnalyzer: ASTPass { if case .dot = binaryExpression.opToken { // The identifier explicitly refers to a state property, such as in `self.foo`. // We set its enclosing type to the type it is declared in. - let enclosingType = enclosingTypeIdentifier(in: passContext) + let enclosingType = passContext.enclosingTypeIdentifier! let lhsType = passContext.environment!.type(of: binaryExpression.lhs, enclosingType: enclosingType.name, scopeContext: passContext.scopeContext!) binaryExpression.rhs = binaryExpression.rhs.assigningEnclosingType(type: lhsType.name) } @@ -334,7 +332,7 @@ public struct SemanticAnalyzer: ASTPass { private func isStorageReference(expression: Expression, scopeContext: ScopeContext) -> Bool { switch expression { case .self(_): return true - case .identifier(let identifier): return !scopeContext.containsVariableDeclaration(for: identifier.name) + case .identifier(let identifier): return !scopeContext.containsDeclaration(for: identifier.name) case .inoutExpression(let inoutExpression): return isStorageReference(expression: inoutExpression.expression, scopeContext: scopeContext) case .binaryExpression(let binaryExpression): return isStorageReference(expression: binaryExpression.lhs, scopeContext: scopeContext) @@ -399,6 +397,9 @@ public struct SemanticAnalyzer: ASTPass { // Clear the context in preparation for the next time we visit a function declaration. let passContext = passContext.withUpdates { $0.mutatingExpressions = nil } + + var functionDeclaration = functionDeclaration + functionDeclaration.scopeContext = passContext.scopeContext return ASTPassResult(element: functionDeclaration, diagnostics: diagnostics, passContext: passContext) } @@ -434,6 +435,8 @@ public struct SemanticAnalyzer: ASTPass { } } + var initializerDeclaration = initializerDeclaration + initializerDeclaration.scopeContext = passContext.scopeContext return ASTPassResult(element: initializerDeclaration, diagnostics: diagnostics, passContext: passContext) } @@ -482,7 +485,7 @@ public struct SemanticAnalyzer: ASTPass { var passContext = passContext let environment = passContext.environment! - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name let callerCapabilities = passContext.contractBehaviorDeclarationContext?.callerCapabilities ?? [] var diagnostics = [Diagnostic]() diff --git a/Sources/SemanticAnalyzer/TypeChecker.swift b/Sources/SemanticAnalyzer/TypeChecker.swift index 9a2204b5..f62a99f2 100644 --- a/Sources/SemanticAnalyzer/TypeChecker.swift +++ b/Sources/SemanticAnalyzer/TypeChecker.swift @@ -128,7 +128,7 @@ public struct TypeChecker: ASTPass { // In an assignment, ensure the LHS and RHS have the same type. if case .punctuation(.equal) = binaryExpression.op.kind { - let typeIdentifier = enclosingTypeIdentifier(in: passContext) + let typeIdentifier = passContext.enclosingTypeIdentifier! let lhsType = environment.type(of: binaryExpression.lhs, enclosingType: typeIdentifier.name, scopeContext: passContext.scopeContext!) let rhsType = environment.type(of: binaryExpression.rhs, enclosingType: typeIdentifier.name, scopeContext: passContext.scopeContext!) @@ -144,7 +144,7 @@ public struct TypeChecker: ASTPass { public func process(functionCall: FunctionCall, passContext: ASTPassContext) -> ASTPassResult { var diagnostics = [Diagnostic]() let environment = passContext.environment! - let enclosingType = enclosingTypeIdentifier(in: passContext).name + let enclosingType = passContext.enclosingTypeIdentifier!.name if let eventCall = environment.matchEventCall(functionCall, enclosingType: enclosingType) { let expectedTypes = eventCall.typeGenericArguments @@ -177,7 +177,7 @@ public struct TypeChecker: ASTPass { public func process(returnStatement: ReturnStatement, passContext: ASTPassContext) -> ASTPassResult { var diagnostics = [Diagnostic]() - let typeIdentifier = enclosingTypeIdentifier(in: passContext) + let typeIdentifier = passContext.enclosingTypeIdentifier! let functionDeclarationContext = passContext.functionDeclarationContext! let environment = passContext.environment! diff --git a/Tests/BehaviorTests/tests/currency/currency.flint b/Tests/BehaviorTests/tests/currency/currency.flint new file mode 100644 index 00000000..97620b1b --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/currency.flint @@ -0,0 +1,48 @@ +struct Coin { + var value: Int + + init(amount: Int) { + self.value = amount + } + + mutating func transfer(other: inout Coin, amount: Int) { + if other.value >= amount { + value += amount + other.value -= amount + } + } +} + +contract C { + var account1: Coin + var account2: Coin +} + +C :: (any) { + + public mutating func mint1(amount: Int) { + account1 = Coin(amount) + } + + public mutating func mint2(amount: Int) { + account2 = Coin(amount) + } + + mutating public func transfer1(amount: Int) { + let w: Coin = Coin(0) + w.transfer(&account1, amount) + account2.transfer(&w, amount) + } + + mutating public func transfer2(amount: Int) { + account1.transfer(&account2, amount) + } + + public func get1() -> Int { + return account1.value + } + + public func get2() -> Int { + return account2.value + } +} diff --git a/Tests/BehaviorTests/tests/currency/test/config.js b/Tests/BehaviorTests/tests/currency/test/config.js new file mode 100644 index 00000000..0cbf3bda --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/test/config.js @@ -0,0 +1 @@ +exports.contractName = "C" diff --git a/Tests/BehaviorTests/tests/currency/test/migrations/1.js b/Tests/BehaviorTests/tests/currency/test/migrations/1.js new file mode 100644 index 00000000..38a8b644 --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/test/migrations/1.js @@ -0,0 +1,7 @@ +var config = require("../config.js") + +var Contract = artifacts.require("./" + config.contractName + ".sol"); + +module.exports = function(deployer) { + deployer.deploy(Contract); +}; diff --git a/Tests/BehaviorTests/tests/currency/test/test/.placeholder b/Tests/BehaviorTests/tests/currency/test/test/.placeholder new file mode 100644 index 00000000..3ed31e18 --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/test/test/.placeholder @@ -0,0 +1 @@ +This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove. diff --git a/Tests/BehaviorTests/tests/currency/test/test/test.js b/Tests/BehaviorTests/tests/currency/test/test/test.js new file mode 100644 index 00000000..26442899 --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/test/test/test.js @@ -0,0 +1,50 @@ +// RUN: cd %S && truffle test + +var config = require("../config.js") + +var Contract = artifacts.require("./" + config.contractName + ".sol"); +var Interface = artifacts.require("./_Interface" + config.contractName + ".sol"); +Contract.abi = Interface.abi + +contract(config.contractName, function(accounts) { + it("should correctly mint account1", async function() { + const instance = await Contract.deployed(); + let t; + + await instance.mint1(20); + + t = await instance.get1(); + assert.equal(t.valueOf(), 20); + + t = await instance.get2(); + assert.equal(t.valueOf(), 0); + }); + + it("should transfer funds from account1 to account2", async function() { + const instance = await Contract.deployed(); + let t; + + await instance.transfer1(5); + + t = await instance.get1(); + assert.equal(t.valueOf(), 15); + + t = await instance.get2(); + assert.equal(t.valueOf(), 5); + }); + + it("should transfer funds from account2 to account1", async function() { + const instance = await Contract.deployed(); + let t; + + await instance.transfer2(2); + + t = await instance.get1(); + assert.equal(t.valueOf(), 17); + + t = await instance.get2(); + assert.equal(t.valueOf(), 3); + }); +}); + + diff --git a/Tests/BehaviorTests/tests/currency/test/truffle-config.js b/Tests/BehaviorTests/tests/currency/test/truffle-config.js new file mode 100644 index 00000000..a6330d6d --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/test/truffle-config.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +}; diff --git a/Tests/BehaviorTests/tests/currency/test/truffle.js b/Tests/BehaviorTests/tests/currency/test/truffle.js new file mode 100644 index 00000000..a6330d6d --- /dev/null +++ b/Tests/BehaviorTests/tests/currency/test/truffle.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +}; diff --git a/Tests/BehaviorTests/tests/memory/memory.flint b/Tests/BehaviorTests/tests/memory/memory.flint new file mode 100644 index 00000000..7ebb3594 --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/memory.flint @@ -0,0 +1,78 @@ +contract C { + var s: S + var t: T +} + +C :: (any) { + public mutating func setS(a: Int, b: String) { + let s: S = S(a, b) + s.incrementA() + self.s = S(s.a, s.s) + } + + public mutating func setT1(a: Int) { + let t1: T = T(a) + let t2: T = T(a + 1) + + self.t = T(t1.x) + } + + public mutating func setT2(a: Int) { + let t1: T = T(a) + let t2: T = T(a + 1) + + self.t = T(t2.x) + } + + public mutating func setT3(c: Bool, a: Int, b: Int) { + if c { + let t: T = T(a) + t.increment() + self.t = T(t.x) + } else { + let t: T = T(b) + t.increment() + self.t = T(t.x) + } + } + + public func getSa() -> Int { + return s.a + } + + public func getSs() -> String { + return s.s + } + + public func getTx() -> Int { + return t.x + } +} + +struct S { + var a: Int = 10 + var b: Int = 3 + var s: String + var t: T + + init(a: Int, s: String) { + self.a = a + self.s = s + } + + mutating func incrementA() { + self.a += 1 + } +} + +struct T { + var x: Int = 2 + + init(x: Int) { + self.x = x + } + + mutating func increment() { + self.x += 1 + } +} diff --git a/Tests/BehaviorTests/tests/memory/test/config.js b/Tests/BehaviorTests/tests/memory/test/config.js new file mode 100644 index 00000000..0cbf3bda --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/test/config.js @@ -0,0 +1 @@ +exports.contractName = "C" diff --git a/Tests/BehaviorTests/tests/memory/test/migrations/1.js b/Tests/BehaviorTests/tests/memory/test/migrations/1.js new file mode 100644 index 00000000..38a8b644 --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/test/migrations/1.js @@ -0,0 +1,7 @@ +var config = require("../config.js") + +var Contract = artifacts.require("./" + config.contractName + ".sol"); + +module.exports = function(deployer) { + deployer.deploy(Contract); +}; diff --git a/Tests/BehaviorTests/tests/memory/test/test/.placeholder b/Tests/BehaviorTests/tests/memory/test/test/.placeholder new file mode 100644 index 00000000..3ed31e18 --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/test/test/.placeholder @@ -0,0 +1 @@ +This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove. diff --git a/Tests/BehaviorTests/tests/memory/test/test/test.js b/Tests/BehaviorTests/tests/memory/test/test/test.js new file mode 100644 index 00000000..f0d1fc15 --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/test/test/test.js @@ -0,0 +1,54 @@ +// RUN: cd %S && truffle test + +var config = require("../config.js") + +var Contract = artifacts.require("./" + config.contractName + ".sol"); +var Interface = artifacts.require("./_Interface" + config.contractName + ".sol"); +Contract.abi = Interface.abi + +contract(config.contractName, function(accounts) { + it("should be possible to record information in local structs", async function() { + const instance = await Contract.deployed() + let t; + + await instance.setS(1, "hello"); + + t = await instance.getSa() + assert.equal(t.valueOf(), 2); + + t = await instance.getSs() + assert.equal(web3.toUtf8(t.valueOf()), "hello"); + }); + + it("should not override memory of other local structs", async function() { + const instance = await Contract.deployed() + let t; + + await instance.setT1(5); + + t = await instance.getTx() + assert.equal(t.valueOf(), 5); + + await instance.setT2(5); + + t = await instance.getTx() + assert.equal(t.valueOf(), 6); + }); + + it("support branching", async function() { + const instance = await Contract.deployed() + let t; + + await instance.setT3(0, 2, 3); + + t = await instance.getTx() + assert.equal(t.valueOf(), 4); + + await instance.setT3(1, 2, 3); + + t = await instance.getTx() + assert.equal(t.valueOf(), 3); + }); +}); + + diff --git a/Tests/BehaviorTests/tests/memory/test/truffle-config.js b/Tests/BehaviorTests/tests/memory/test/truffle-config.js new file mode 100644 index 00000000..a6330d6d --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/test/truffle-config.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +}; diff --git a/Tests/BehaviorTests/tests/memory/test/truffle.js b/Tests/BehaviorTests/tests/memory/test/truffle.js new file mode 100644 index 00000000..a6330d6d --- /dev/null +++ b/Tests/BehaviorTests/tests/memory/test/truffle.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +};