| OLD | NEW |
| 1 -- Copyright 2011 the V8 project authors. All rights reserved. | 1 -- Copyright 2011 the V8 project authors. All rights reserved. |
| 2 -- Redistribution and use in source and binary forms, with or without | 2 -- Redistribution and use in source and binary forms, with or without |
| 3 -- modification, are permitted provided that the following conditions are | 3 -- modification, are permitted provided that the following conditions are |
| 4 -- met: | 4 -- met: |
| 5 -- | 5 -- |
| 6 -- * Redistributions of source code must retain the above copyright | 6 -- * Redistributions of source code must retain the above copyright |
| 7 -- notice, this list of conditions and the following disclaimer. | 7 -- notice, this list of conditions and the following disclaimer. |
| 8 -- * Redistributions in binary form must reproduce the above | 8 -- * Redistributions in binary form must reproduce the above |
| 9 -- copyright notice, this list of conditions and the following | 9 -- copyright notice, this list of conditions and the following |
| 10 -- disclaimer in the documentation and/or other materials provided | 10 -- disclaimer in the documentation and/or other materials provided |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 22 -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 23 -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 24 -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 25 -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 26 -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | 27 |
| 28 -- This is main driver for gcmole tool. See README for more details. | 28 -- This is main driver for gcmole tool. See README for more details. |
| 29 -- Usage: CLANG_BIN=clang-bin-dir lua tools/gcmole/gcmole.lua [arm|ia32|x64] | 29 -- Usage: CLANG_BIN=clang-bin-dir lua tools/gcmole/gcmole.lua [arm|ia32|x64] |
| 30 | 30 |
| 31 local DIR = arg[0]:match("^(.+)/[^/]+$") | 31 local DIR = arg[0]:match("^(.+)/[^/]+$") |
| 32 | 32 |
| 33 local ARCHS = arg[1] and { arg[1] } or { 'ia32', 'arm', 'x64' } | 33 local FLAGS = { |
| 34 -- Do not build gcsuspects file and reuse previously generated one. |
| 35 reuse_gcsuspects = false; |
| 36 |
| 37 -- Print commands to console before executing them. |
| 38 verbose = false; |
| 39 |
| 40 -- Perform dead variable analysis (generates many false positives). |
| 41 -- TODO add some sort of whiteliste to filter out false positives. |
| 42 dead_vars = false; |
| 43 |
| 44 -- When building gcsuspects whitelist certain functions as if they |
| 45 -- can be causing GC. Currently used to reduce number of false |
| 46 -- positives in dead variables analysis. See TODO for WHITELIST |
| 47 -- below. |
| 48 whitelist = true; |
| 49 } |
| 50 local ARGS = {} |
| 51 |
| 52 for i = 1, #arg do |
| 53 local flag = arg[i]:match "^%-%-([%w_-]+)$" |
| 54 if flag then |
| 55 local no, real_flag = flag:match "^(no)([%w_-]+)$" |
| 56 if real_flag then flag = real_flag end |
| 57 |
| 58 flag = flag:gsub("%-", "_") |
| 59 if FLAGS[flag] ~= nil then |
| 60 FLAGS[flag] = (no ~= "no") |
| 61 else |
| 62 error("Unknown flag: " .. flag) |
| 63 end |
| 64 else |
| 65 table.insert(ARGS, arg[i]) |
| 66 end |
| 67 end |
| 68 |
| 69 local ARCHS = ARGS[1] and { ARGS[1] } or { 'ia32', 'arm', 'x64' } |
| 34 | 70 |
| 35 local io = require "io" | 71 local io = require "io" |
| 36 local os = require "os" | 72 local os = require "os" |
| 37 | 73 |
| 38 function log(...) | 74 function log(...) |
| 39 io.stderr:write(string.format(...)) | 75 io.stderr:write(string.format(...)) |
| 40 io.stderr:write "\n" | 76 io.stderr:write "\n" |
| 41 end | 77 end |
| 42 | 78 |
| 43 ------------------------------------------------------------------------------- | 79 ------------------------------------------------------------------------------- |
| 44 -- Clang invocation | 80 -- Clang invocation |
| 45 | 81 |
| 46 local CLANG_BIN = os.getenv "CLANG_BIN" | 82 local CLANG_BIN = os.getenv "CLANG_BIN" |
| 47 | 83 |
| 48 if not CLANG_BIN or CLANG_BIN == "" then | 84 if not CLANG_BIN or CLANG_BIN == "" then |
| 49 error "CLANG_BIN not set" | 85 error "CLANG_BIN not set" |
| 50 end | 86 end |
| 51 | 87 |
| 52 local function MakeClangCommandLine(plugin, triple, arch_define) | 88 local function MakeClangCommandLine(plugin, plugin_args, triple, arch_define) |
| 53 return CLANG_BIN .. "/clang -cc1 -load " .. DIR .. "/libgcmole.so" | 89 if plugin_args then |
| 90 for i = 1, #plugin_args do |
| 91 plugin_args[i] = "-plugin-arg-" .. plugin .. " " .. plugin_args[i] |
| 92 end |
| 93 plugin_args = " " .. table.concat(plugin_args, " ") |
| 94 end |
| 95 return CLANG_BIN .. "/clang -cc1 -load " .. DIR .. "/libgcmole.so" |
| 54 .. " -plugin " .. plugin | 96 .. " -plugin " .. plugin |
| 55 .. " -triple " .. triple | 97 .. (plugin_args or "") |
| 98 .. " -triple " .. triple |
| 56 .. " -D" .. arch_define | 99 .. " -D" .. arch_define |
| 57 .. " -DENABLE_VMSTATE_TRACKING" | 100 .. " -DENABLE_VMSTATE_TRACKING" |
| 58 .. " -DENABLE_LOGGING_AND_PROFILING" | 101 .. " -DENABLE_LOGGING_AND_PROFILING" |
| 59 .. " -DENABLE_DEBUGGER_SUPPORT" | 102 .. " -DENABLE_DEBUGGER_SUPPORT" |
| 60 .. " -Isrc" | 103 .. " -Isrc" |
| 61 end | 104 end |
| 62 | 105 |
| 63 function InvokeClangPluginForEachFile(filenames, cfg, func) | 106 function InvokeClangPluginForEachFile(filenames, cfg, func) |
| 64 local cmd_line = MakeClangCommandLine(cfg.plugin, | 107 local cmd_line = MakeClangCommandLine(cfg.plugin, |
| 65 » » » » » cfg.triple, | 108 cfg.plugin_args, |
| 66 » » » » » cfg.arch_define) | 109 cfg.triple, |
| 110 cfg.arch_define) |
| 67 | 111 |
| 68 for _, filename in ipairs(filenames) do | 112 for _, filename in ipairs(filenames) do |
| 69 log("-- %s", filename) | 113 log("-- %s", filename) |
| 70 | |
| 71 local action = cmd_line .. " src/" .. filename .. " 2>&1" | 114 local action = cmd_line .. " src/" .. filename .. " 2>&1" |
| 72 | 115 if FLAGS.verbose then print('popen ', action) end |
| 73 local pipe = io.popen(action) | 116 local pipe = io.popen(action) |
| 74 func(filename, pipe:lines()) | 117 func(filename, pipe:lines()) |
| 75 pipe:close() | 118 pipe:close() |
| 76 end | 119 end |
| 77 end | 120 end |
| 78 | 121 |
| 79 ------------------------------------------------------------------------------- | 122 ------------------------------------------------------------------------------- |
| 80 -- SConscript parsing | 123 -- SConscript parsing |
| 81 | 124 |
| 82 local function ParseSConscript() | 125 local function ParseSConscript() |
| 83 local f = assert(io.open("src/SConscript"), "failed to open SConscript") | 126 local f = assert(io.open("src/SConscript"), "failed to open SConscript") |
| 84 local sconscript = f:read('*a') | 127 local sconscript = f:read('*a') |
| 85 f:close() | 128 f:close() |
| 86 | 129 |
| 87 local SOURCES = sconscript:match "SOURCES = {(.-)}"; | 130 local SOURCES = sconscript:match "SOURCES = {(.-)}"; |
| 88 | 131 |
| 89 local sources = {} | 132 local sources = {} |
| 90 | 133 |
| 91 for condition, list in | 134 for condition, list in |
| 92 SOURCES:gmatch "'([^']-)': Split%(\"\"\"(.-)\"\"\"%)" do | 135 SOURCES:gmatch "'([^']-)': Split%(\"\"\"(.-)\"\"\"%)" do |
| 93 local files = {} | 136 local files = {} |
| 94 for file in list:gmatch "[^%s]+" do table.insert(files, file) end | 137 for file in list:gmatch "[^%s]+" do table.insert(files, file) end |
| 95 sources[condition] = files | 138 sources[condition] = files |
| 96 end | 139 end |
| 97 | 140 |
| 98 for condition, list in SOURCES:gmatch "'([^']-)': %[(.-)%]" do | 141 for condition, list in SOURCES:gmatch "'([^']-)': %[(.-)%]" do |
| 99 local files = {} | 142 local files = {} |
| 100 for file in list:gmatch "'([^']-)'" do table.insert(files, file) end | 143 for file in list:gmatch "'([^']-)'" do table.insert(files, file) end |
| 101 sources[condition] = files | 144 sources[condition] = files |
| 102 end | 145 end |
| 103 | 146 |
| 104 return sources | 147 return sources |
| 105 end | 148 end |
| 106 | 149 |
| 107 local function EvaluateCondition(cond, props) | 150 local function EvaluateCondition(cond, props) |
| 108 if cond == 'all' then return true end | 151 if cond == 'all' then return true end |
| 109 | 152 |
| 110 local p, v = cond:match "(%w+):(%w+)" | 153 local p, v = cond:match "(%w+):(%w+)" |
| 111 | 154 |
| 112 assert(p and v, "failed to parse condition: " .. cond) | 155 assert(p and v, "failed to parse condition: " .. cond) |
| 113 assert(props[p] ~= nil, "undefined configuration property: " .. p) | 156 assert(props[p] ~= nil, "undefined configuration property: " .. p) |
| 114 | 157 |
| 115 return props[p] == v | 158 return props[p] == v |
| 116 end | 159 end |
| 117 | 160 |
| 118 local function BuildFileList(sources, props) | 161 local function BuildFileList(sources, props) |
| 119 local list = {} | 162 local list = {} |
| 120 for condition, files in pairs(sources) do | 163 for condition, files in pairs(sources) do |
| 121 if EvaluateCondition(condition, props) then | 164 if EvaluateCondition(condition, props) then |
| 122 » for i = 1, #files do table.insert(list, files[i]) end | 165 for i = 1, #files do table.insert(list, files[i]) end |
| 123 end | 166 end |
| 124 end | 167 end |
| 125 return list | 168 return list |
| 126 end | 169 end |
| 127 | 170 |
| 128 local sources = ParseSConscript() | 171 local sources = ParseSConscript() |
| 129 | 172 |
| 130 local function FilesForArch(arch) | 173 local function FilesForArch(arch) |
| 131 return BuildFileList(sources, { os = 'linux', | 174 return BuildFileList(sources, { os = 'linux', |
| 132 » » » » arch = arch, | 175 arch = arch, |
| 133 » » » » mode = 'debug', | 176 mode = 'debug', |
| 134 » » » » simulator = ''}) | 177 simulator = ''}) |
| 135 end | 178 end |
| 136 | 179 |
| 137 local mtConfig = {} | 180 local mtConfig = {} |
| 138 | 181 |
| 139 mtConfig.__index = mtConfig | 182 mtConfig.__index = mtConfig |
| 140 | 183 |
| 141 local function config (t) return setmetatable(t, mtConfig) end | 184 local function config (t) return setmetatable(t, mtConfig) end |
| 142 | 185 |
| 143 function mtConfig:extend(t) | 186 function mtConfig:extend(t) |
| 144 local e = {} | 187 local e = {} |
| 145 for k, v in pairs(self) do e[k] = v end | 188 for k, v in pairs(self) do e[k] = v end |
| 146 for k, v in pairs(t) do e[k] = v end | 189 for k, v in pairs(t) do e[k] = v end |
| 147 return config(e) | 190 return config(e) |
| 148 end | 191 end |
| 149 | 192 |
| 150 local ARCHITECTURES = { | 193 local ARCHITECTURES = { |
| 151 ia32 = config { triple = "i586-unknown-linux", | 194 ia32 = config { triple = "i586-unknown-linux", |
| 152 » » arch_define = "V8_TARGET_ARCH_IA32" }, | 195 arch_define = "V8_TARGET_ARCH_IA32" }, |
| 153 arm = config { triple = "i586-unknown-linux", | 196 arm = config { triple = "i586-unknown-linux", |
| 154 » » arch_define = "V8_TARGET_ARCH_ARM" }, | 197 arch_define = "V8_TARGET_ARCH_ARM" }, |
| 155 x64 = config { triple = "x86_64-unknown-linux", | 198 x64 = config { triple = "x86_64-unknown-linux", |
| 156 » » arch_define = "V8_TARGET_ARCH_X64" } | 199 arch_define = "V8_TARGET_ARCH_X64" } |
| 157 } | 200 } |
| 158 | 201 |
| 159 ------------------------------------------------------------------------------- | 202 ------------------------------------------------------------------------------- |
| 160 -- GCSuspects Generation | 203 -- GCSuspects Generation |
| 161 | 204 |
| 162 local gc = {} | 205 local gc, gc_caused, funcs |
| 163 local funcs = {} | 206 |
| 207 local WHITELIST = { |
| 208 -- The following functions call CEntryStub which is always present. |
| 209 "MacroAssembler.*CallExternalReference", |
| 210 "MacroAssembler.*CallRuntime", |
| 211 "CompileCallLoadPropertyWithInterceptor", |
| 212 "CallIC.*GenerateMiss", |
| 213 |
| 214 -- DirectCEntryStub is a special stub used on ARM. |
| 215 -- It is pinned and always present. |
| 216 "DirectCEntryStub.*GenerateCall", |
| 217 |
| 218 -- TODO GCMole currently is sensitive enough to understand that certain |
| 219 -- functions only cause GC and return Failure simulataneously. |
| 220 -- Callsites of such functions are safe as long as they are properly |
| 221 -- check return value and propagate the Failure to the caller. |
| 222 -- It should be possible to extend GCMole to understand this. |
| 223 "Heap.*AllocateFunctionPrototype" |
| 224 }; |
| 225 |
| 226 local function AddCause(name, cause) |
| 227 local t = gc_caused[name] |
| 228 if not t then |
| 229 t = {} |
| 230 gc_caused[name] = t |
| 231 end |
| 232 table.insert(t, cause) |
| 233 end |
| 164 | 234 |
| 165 local function resolve(name) | 235 local function resolve(name) |
| 166 local f = funcs[name] | 236 local f = funcs[name] |
| 167 | 237 |
| 168 if not f then | 238 if not f then |
| 169 f = {} | 239 f = {} |
| 170 funcs[name] = f | 240 funcs[name] = f |
| 171 | 241 |
| 172 if name:match "Collect.*Garbage" then gc[name] = true end | 242 if name:match "Collect.*Garbage" then |
| 243 gc[name] = true |
| 244 AddCause(name, "<GC>") |
| 245 end |
| 246 |
| 247 if FLAGS.whitelist then |
| 248 for i = 1, #WHITELIST do |
| 249 if name:match(WHITELIST[i]) then |
| 250 gc[name] = false |
| 251 end |
| 252 end |
| 253 end |
| 173 end | 254 end |
| 174 | 255 |
| 175 return f | 256 return f |
| 176 end | 257 end |
| 177 | 258 |
| 178 local function parse (filename, lines) | 259 local function parse (filename, lines) |
| 179 local scope | 260 local scope |
| 180 | 261 |
| 181 for funcname in lines do | 262 for funcname in lines do |
| 182 if funcname:sub(1, 1) ~= '\t' then | 263 if funcname:sub(1, 1) ~= '\t' then |
| 183 » resolve(funcname) | 264 resolve(funcname) |
| 184 » scope = funcname | 265 scope = funcname |
| 185 else | 266 else |
| 186 » local name = funcname:sub(2) | 267 local name = funcname:sub(2) |
| 187 » resolve(name)[scope] = true | 268 resolve(name)[scope] = true |
| 188 end | 269 end |
| 189 end | 270 end |
| 190 end | 271 end |
| 191 | 272 |
| 192 local function propagate () | 273 local function propagate () |
| 193 log "** Propagating GC information" | 274 log "** Propagating GC information" |
| 194 | 275 |
| 195 local function mark(callers) | 276 local function mark(from, callers) |
| 196 for caller, _ in pairs(callers) do | 277 for caller, _ in pairs(callers) do |
| 197 » if not gc[caller] then | 278 if gc[caller] == nil then |
| 198 » gc[caller] = true | 279 gc[caller] = true |
| 199 » mark(funcs[caller]) | 280 mark(caller, funcs[caller]) |
| 200 » end | 281 end |
| 282 AddCause(caller, from) |
| 201 end | 283 end |
| 202 end | 284 end |
| 203 | 285 |
| 204 for funcname, callers in pairs(funcs) do | 286 for funcname, callers in pairs(funcs) do |
| 205 if gc[funcname] then mark(callers) end | 287 if gc[funcname] then mark(funcname, callers) end |
| 206 end | 288 end |
| 207 end | 289 end |
| 208 | 290 |
| 209 local function GenerateGCSuspects(arch, files, cfg) | 291 local function GenerateGCSuspects(arch, files, cfg) |
| 292 -- Reset the global state. |
| 293 gc, gc_caused, funcs = {}, {}, {} |
| 294 |
| 210 log ("** Building GC Suspects for %s", arch) | 295 log ("** Building GC Suspects for %s", arch) |
| 211 InvokeClangPluginForEachFile (files, | 296 InvokeClangPluginForEachFile (files, |
| 212 cfg:extend { plugin = "dump-callees" }, | 297 cfg:extend { plugin = "dump-callees" }, |
| 213 parse) | 298 parse) |
| 214 | 299 |
| 215 propagate() | 300 propagate() |
| 216 | 301 |
| 217 local out = assert(io.open("gcsuspects", "w")) | 302 local out = assert(io.open("gcsuspects", "w")) |
| 218 for name, _ in pairs(gc) do out:write (name, '\n') end | 303 for name, value in pairs(gc) do if value then out:write (name, '\n') end end |
| 219 out:close() | 304 out:close() |
| 305 |
| 306 local out = assert(io.open("gccauses", "w")) |
| 307 out:write "GC = {" |
| 308 for name, causes in pairs(gc_caused) do |
| 309 out:write("['", name, "'] = {") |
| 310 for i = 1, #causes do out:write ("'", causes[i], "';") end |
| 311 out:write("};\n") |
| 312 end |
| 313 out:write "}" |
| 314 out:close() |
| 315 |
| 220 log ("** GCSuspects generated for %s", arch) | 316 log ("** GCSuspects generated for %s", arch) |
| 221 end | 317 end |
| 222 | 318 |
| 223 ------------------------------------------------------------------------------- | 319 -------------------------------------------------------------------------------- |
| 224 -- Analysis | 320 -- Analysis |
| 225 | 321 |
| 226 local function CheckCorrectnessForArch(arch) | 322 local function CheckCorrectnessForArch(arch) |
| 227 local files = FilesForArch(arch) | 323 local files = FilesForArch(arch) |
| 228 local cfg = ARCHITECTURES[arch] | 324 local cfg = ARCHITECTURES[arch] |
| 229 | 325 |
| 230 GenerateGCSuspects(arch, files, cfg) | 326 if not FLAGS.reuse_gcsuspects then |
| 327 GenerateGCSuspects(arch, files, cfg) |
| 328 end |
| 231 | 329 |
| 232 local processed_files = 0 | 330 local processed_files = 0 |
| 233 local errors_found = false | 331 local errors_found = false |
| 234 local function SearchForErrors(filename, lines) | 332 local function SearchForErrors(filename, lines) |
| 235 processed_files = processed_files + 1 | 333 processed_files = processed_files + 1 |
| 236 for l in lines do | 334 for l in lines do |
| 237 » errors_found = errors_found or | 335 errors_found = errors_found or |
| 238 » l:match "^[^:]+:%d+:%d+:" or | 336 l:match "^[^:]+:%d+:%d+:" or |
| 239 » l:match "error" or | 337 l:match "error" or |
| 240 » l:match "warning" | 338 l:match "warning" |
| 241 print(l) | 339 print(l) |
| 242 end | 340 end |
| 243 end | 341 end |
| 244 | 342 |
| 245 log("** Searching for evaluation order problems for %s", arch) | 343 log("** Searching for evaluation order problems%s for %s", |
| 344 FLAGS.dead_vars and " and dead variables" or "", |
| 345 arch) |
| 346 local plugin_args |
| 347 if FLAGS.dead_vars then plugin_args = { "--dead-vars" } end |
| 246 InvokeClangPluginForEachFile(files, | 348 InvokeClangPluginForEachFile(files, |
| 247 » » » » cfg:extend { plugin = "find-problems" }, | 349 cfg:extend { plugin = "find-problems", |
| 248 » » » SearchForErrors) | 350 plugin_args = plugin_args }, |
| 351 SearchForErrors) |
| 249 log("** Done processing %d files. %s", | 352 log("** Done processing %d files. %s", |
| 250 processed_files, | 353 processed_files, |
| 251 errors_found and "Errors found" or "No errors found") | 354 errors_found and "Errors found" or "No errors found") |
| 252 | 355 |
| 253 return errors_found | 356 return errors_found |
| 254 end | 357 end |
| 255 | 358 |
| 256 local function SafeCheckCorrectnessForArch(arch) | 359 local function SafeCheckCorrectnessForArch(arch) |
| 257 local status, errors = pcall(CheckCorrectnessForArch, arch) | 360 local status, errors = pcall(CheckCorrectnessForArch, arch) |
| 258 if not status then | 361 if not status then |
| 259 print(string.format("There was an error: %s", errors)) | 362 print(string.format("There was an error: %s", errors)) |
| 260 errors = true | 363 errors = true |
| 261 end | 364 end |
| 262 return errors | 365 return errors |
| 263 end | 366 end |
| 264 | 367 |
| 265 local errors = false | 368 local errors = false |
| 266 | 369 |
| 267 for _, arch in ipairs(ARCHS) do | 370 for _, arch in ipairs(ARCHS) do |
| 268 if not ARCHITECTURES[arch] then | 371 if not ARCHITECTURES[arch] then |
| 269 error ("Unknown arch: " .. arch) | 372 error ("Unknown arch: " .. arch) |
| 270 end | 373 end |
| 271 | 374 |
| 272 errors = SafeCheckCorrectnessForArch(arch, report) or errors | 375 errors = SafeCheckCorrectnessForArch(arch, report) or errors |
| 273 end | 376 end |
| 274 | 377 |
| 275 os.exit(errors and 1 or 0) | 378 os.exit(errors and 1 or 0) |
| OLD | NEW |