Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiler: improve boundary analysis and optimizations #110

Merged
merged 14 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions src/julec/opt/boundary.jule
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright 2024 The Jule Programming Language.
// Use of this source code is governed by a BSD 3-Clause
// license that can be found in the LICENSE file.

use std::jule::constant::{Const}
use std::jule::lex::{TokenId}
use std::jule::sema::{Var, StructSubIdentExprModel, UnaryExprModel, ExprModel, TypeKind}

const invalidBoundary = 0x0

struct boundaryVar {
var: uintptr
maxSize: []ExprModel
}

// Information wrapper for boundary analysis.
struct boundary {
vars: []&boundaryVar
}

impl boundary {
// Appends variable with initial maximum size expression.
// If variable is already exist, updates maximum size information.
fn pushVar(mut self, var: uintptr, mut maxSize: ExprModel) {
if !Access || var == invalidBoundary {
// Ignore it, because this optimizations within scope of the --opt-access flag.
ret
}
if !isValidBoundaryInfo(maxSize) {
ret
}
for (_, mut v) in self.vars {
if v.var == var {
for i, max in v.maxSize {
if fitsSize(maxSize, max) {
// Maximum size is fits, so new size is larger than current size.
v.maxSize[i] = maxSize
ret
}
}
v.maxSize = append(v.maxSize, maxSize)
ret
}
}
// Not exist, append new one.
for (_, mut v) in self.vars {
if v.var == invalidBoundary {
// Empty place, use here instead of append.
v.var = var
v.maxSize = append(v.maxSize, maxSize)
ret
}
}
self.vars = append(self.vars, &boundaryVar{var: var, maxSize: [maxSize]})
}

fn removeVar(mut self, var: uintptr): bool {
if var != invalidBoundary {
for (_, mut v) in self.vars {
if v.var == var {
v.var = invalidBoundary
v.maxSize = v.maxSize[:0]
ret true
}
}
}
ret false
}

// Reports whether maximum size of variable is fits with given expression.
fn fitsMaxSize(mut self, var: uintptr, expr: ExprModel): bool {
if var != invalidBoundary {
for _, v in self.vars {
if v.var == var {
ret fitsMaxSize(v.maxSize, expr) != -1
}
}
}
ret false
}
}

// Reports whether model is valid maximum size information for boundary analysis.
fn isValidBoundaryInfo(m: ExprModel): bool { ret fitsSize(m, m) }

// Reports whether maximum size is fits with given expression.
// In other words, reports whether: max >= expr
// Returns index number of max size which is fit, otherwise -1.
fn fitsMaxSize(max: []ExprModel, expr: ExprModel): int {
for i, m in max {
if fitsSize(m, expr) {
ret i
}
}
ret -1
}

fn fitsSize(e1: ExprModel, e2: ExprModel): bool {
if typeData(e1) != typeData(e2) {
ret false
}
match type e1 {
| &Const:
exprConst := (&Const)(e2)
ret exprConst.AsF64() >= 0 && (&Const)(e1).GtEq(*exprConst)
| &Var:
ret e1 == e2
| &StructSubIdentExprModel:
ssi1 := (&StructSubIdentExprModel)(e1)
ssi2 := (&StructSubIdentExprModel)(e2)
ret equalModels(ssi1.Expr.Model, ssi2.Expr.Model) && ssi1.Field == ssi2.Field
| &UnaryExprModel:
uem1 := (&UnaryExprModel)(e1)
uem2 := (&UnaryExprModel)(e2)
if uem1.Op.Id != TokenId.Star || uem1.Op.Id != uem2.Op.Id {
ret false
}
ret fitsSize(uem1.Expr.Model, uem2.Expr.Model)
|:
ret false
}
}

fn possibleBoundaryRemove1(mut &b: &boundary, model: ExprModel) {
if b != nil {
b.removeVar(getBoundaryVar(model))
}
}

fn possibleBoundaryRemove2(mut &b: &boundary, model: ExprModel) {
if b == nil {
ret
}
// Clear if size data mutating.
for (_, mut v) in b.vars {
for i, max in v.maxSize {
if equalModels(max, model) {
v.maxSize = append(v.maxSize[:i], v.maxSize[i+1:]...)
break
}
}
}
}

fn possibleBoundaryRemove(mut &b: &boundary, model: ExprModel) {
if b == nil {
ret
}
if b.removeVar(getBoundaryVar(model)) {
// Exist as a variable, so cannot be size data.
// Remove variable and return.
ret
}
// Clear if size data mutating.
for (_, mut v) in b.vars {
for i, max in v.maxSize {
if equalModels(max, model) {
v.maxSize = append(v.maxSize[:i], v.maxSize[i+1:]...)
break
}
}
}
}

fn isBoundaryRiskyType(mut t: &TypeKind): bool { ret t.Sptr() != nil }
fn isBoundaryValidType(mut t: &TypeKind): bool { ret t.Slc() != nil || t.Arr() != nil }

fn getBoundaryVar(m: ExprModel): uintptr {
if !Access {
ret invalidBoundary
}
match type m {
| &Var:
v := (&Var)(m)
if !v.Reference {
// Variable is not reference, return address of it.
ret uintptr((&Var)(m))
}
// Variable is reference, it should be initialized at source code.
// Investigate the initial expression for variable address.
ret getBoundaryVar(v.Value.Data.Model)
| &StructSubIdentExprModel:
ret uintptr((&StructSubIdentExprModel)(m).Field)
| &UnaryExprModel:
uem := (&UnaryExprModel)(m)
if uem.Op.Id == TokenId.Star { // Dereferencing.
ret getBoundaryVar(uem.Expr.Model)
}
}
ret invalidBoundary
}
Loading